Home | History | Annotate | Download | only in camera
      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.content.res.Configuration;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Matrix;
     22 import android.graphics.RectF;
     23 import android.graphics.SurfaceTexture;
     24 import android.view.TextureView;
     25 import android.view.View;
     26 import android.view.View.OnLayoutChangeListener;
     27 
     28 import com.android.camera.app.CameraProvider;
     29 import com.android.camera.debug.Log;
     30 import com.android.camera.settings.Keys;
     31 import com.android.camera.settings.ResolutionUtil;
     32 import com.android.camera.settings.SettingsManager;
     33 import com.android.camera.settings.SettingsUtil;
     34 import com.android.camera.ui.PreviewStatusListener;
     35 import com.android.camera.util.CameraUtil;
     36 import com.android.ex.camera2.portability.CameraDeviceInfo;
     37 import com.android.ex.camera2.portability.Size;
     38 
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 
     42 /**
     43  * This class aims to automate TextureView transform change and notify listeners
     44  * (e.g. bottom bar) of the preview size change.
     45  */
     46 public class TextureViewHelper implements TextureView.SurfaceTextureListener,
     47         OnLayoutChangeListener {
     48 
     49     private static final Log.Tag TAG = new Log.Tag("TexViewHelper");
     50     public static final float MATCH_SCREEN = 0f;
     51     private static final int UNSET = -1;
     52     private final TextureView mPreview;
     53     private final CameraProvider mCameraProvider;
     54     private int mWidth = 0;
     55     private int mHeight = 0;
     56     private RectF mPreviewArea = new RectF();
     57     private float mAspectRatio = MATCH_SCREEN;
     58     private boolean mAutoAdjustTransform = true;
     59     private TextureView.SurfaceTextureListener mSurfaceTextureListener;
     60 
     61     private final ArrayList<PreviewStatusListener.PreviewAspectRatioChangedListener>
     62             mAspectRatioChangedListeners =
     63             new ArrayList<PreviewStatusListener.PreviewAspectRatioChangedListener>();
     64 
     65     private final ArrayList<PreviewStatusListener.PreviewAreaChangedListener>
     66             mPreviewSizeChangedListeners =
     67             new ArrayList<PreviewStatusListener.PreviewAreaChangedListener>();
     68     private OnLayoutChangeListener mOnLayoutChangeListener = null;
     69     private CaptureLayoutHelper mCaptureLayoutHelper = null;
     70     private int mOrientation = UNSET;
     71 
     72     public TextureViewHelper(TextureView preview, CaptureLayoutHelper helper,
     73                              CameraProvider cameraProvider) {
     74         mPreview = preview;
     75         mCameraProvider = cameraProvider;
     76         mPreview.addOnLayoutChangeListener(this);
     77         mPreview.setSurfaceTextureListener(this);
     78         mCaptureLayoutHelper = helper;
     79     }
     80 
     81     /**
     82      * If auto adjust transform is enabled, when there is a layout change, the
     83      * transform matrix will be automatically adjusted based on the preview stream
     84      * aspect ratio in the new layout.
     85      *
     86      * @param enable whether or not auto adjustment should be enabled
     87      */
     88     public void setAutoAdjustTransform(boolean enable) {
     89         mAutoAdjustTransform = enable;
     90     }
     91 
     92     @Override
     93     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
     94                                int oldTop, int oldRight, int oldBottom) {
     95         Log.v(TAG, "onLayoutChange");
     96         int width = right - left;
     97         int height = bottom - top;
     98         int rotation = CameraUtil.getDisplayRotation(mPreview.getContext());
     99         if (mWidth != width || mHeight != height || mOrientation != rotation) {
    100             mWidth = width;
    101             mHeight = height;
    102             mOrientation = rotation;
    103             if (!updateTransform()) {
    104                 clearTransform();
    105             }
    106         }
    107         if (mOnLayoutChangeListener != null) {
    108             mOnLayoutChangeListener.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop,
    109                     oldRight, oldBottom);
    110         }
    111     }
    112 
    113     /**
    114      * Transforms the preview with the identity matrix, ensuring there
    115      * is no scaling on the preview.  It also calls onPreviewSizeChanged, to
    116      * trigger any necessary preview size changing callbacks.
    117      */
    118     public void clearTransform() {
    119         mPreview.setTransform(new Matrix());
    120         mPreviewArea.set(0, 0, mWidth, mHeight);
    121         onPreviewAreaChanged(mPreviewArea);
    122         setAspectRatio(MATCH_SCREEN);
    123     }
    124 
    125     public void updateAspectRatio(float aspectRatio) {
    126         Log.v(TAG, "updateAspectRatio");
    127         if (aspectRatio <= 0) {
    128             Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
    129             return;
    130         }
    131         if (aspectRatio < 1f) {
    132             aspectRatio = 1f / aspectRatio;
    133         }
    134         setAspectRatio(aspectRatio);
    135         updateTransform();
    136     }
    137 
    138     private void setAspectRatio(float aspectRatio) {
    139         Log.v(TAG, "setAspectRatio: " + aspectRatio);
    140         if (mAspectRatio != aspectRatio) {
    141             Log.v(TAG, "aspect ratio changed from: " + mAspectRatio);
    142             mAspectRatio = aspectRatio;
    143             onAspectRatioChanged();
    144         }
    145     }
    146 
    147     private void onAspectRatioChanged() {
    148         mCaptureLayoutHelper.onPreviewAspectRatioChanged(mAspectRatio);
    149         for (PreviewStatusListener.PreviewAspectRatioChangedListener listener
    150                 : mAspectRatioChangedListeners) {
    151             listener.onPreviewAspectRatioChanged(mAspectRatio);
    152         }
    153     }
    154 
    155     public void addAspectRatioChangedListener(
    156             PreviewStatusListener.PreviewAspectRatioChangedListener listener) {
    157         if (listener != null && !mAspectRatioChangedListeners.contains(listener)) {
    158             mAspectRatioChangedListeners.add(listener);
    159         }
    160     }
    161 
    162 
    163     /**
    164      * This returns the rect that is available to display the preview, and
    165      * capture buttons
    166      *
    167      * @return the rect.
    168      */
    169     public RectF getFullscreenRect() {
    170         return mCaptureLayoutHelper.getFullscreenRect();
    171     }
    172 
    173     /**
    174      * This takes a matrix to apply to the texture view and uses the screen
    175      * aspect ratio as the target aspect ratio
    176      *
    177      * @param matrix the matrix to apply
    178      * @param aspectRatio the aspectRatio that the preview should be
    179      */
    180     public void updateTransformFullScreen(Matrix matrix, float aspectRatio) {
    181         aspectRatio = aspectRatio < 1 ? 1 / aspectRatio : aspectRatio;
    182         if (aspectRatio != mAspectRatio) {
    183             setAspectRatio(aspectRatio);
    184         }
    185 
    186         mPreview.setTransform(matrix);
    187         mPreviewArea = mCaptureLayoutHelper.getPreviewRect();
    188         onPreviewAreaChanged(mPreviewArea);
    189 
    190     }
    191 
    192     public void updateTransform(Matrix matrix) {
    193         RectF previewRect = new RectF(0, 0, mWidth, mHeight);
    194         matrix.mapRect(previewRect);
    195 
    196         float previewWidth = previewRect.width();
    197         float previewHeight = previewRect.height();
    198         if (previewHeight == 0 || previewWidth == 0) {
    199             Log.e(TAG, "Invalid preview size: " + previewWidth + " x " + previewHeight);
    200             return;
    201         }
    202         float aspectRatio = previewWidth / previewHeight;
    203         aspectRatio = aspectRatio < 1 ? 1 / aspectRatio : aspectRatio;
    204         if (aspectRatio != mAspectRatio) {
    205             setAspectRatio(aspectRatio);
    206         }
    207 
    208         RectF previewAreaBasedOnAspectRatio = mCaptureLayoutHelper.getPreviewRect();
    209         Matrix addtionalTransform = new Matrix();
    210         addtionalTransform.setRectToRect(previewRect, previewAreaBasedOnAspectRatio,
    211                 Matrix.ScaleToFit.CENTER);
    212         matrix.postConcat(addtionalTransform);
    213         mPreview.setTransform(matrix);
    214         updatePreviewArea(matrix);
    215     }
    216 
    217     /**
    218      * Calculates and updates the preview area rect using the latest transform matrix.
    219      */
    220     private void updatePreviewArea(Matrix matrix) {
    221         mPreviewArea.set(0, 0, mWidth, mHeight);
    222         matrix.mapRect(mPreviewArea);
    223         onPreviewAreaChanged(mPreviewArea);
    224     }
    225 
    226     public void setOnLayoutChangeListener(OnLayoutChangeListener listener) {
    227         mOnLayoutChangeListener = listener;
    228     }
    229 
    230     public void setSurfaceTextureListener(TextureView.SurfaceTextureListener listener) {
    231         mSurfaceTextureListener = listener;
    232     }
    233 
    234     /**
    235      * Updates the transform matrix based current width and height of TextureView
    236      * and preview stream aspect ratio.
    237      *
    238      * <p>If not {@code mAutoAdjustTransform}, this does nothing except return
    239      * {@code false}. In all other cases, it returns {@code true}, regardless of
    240      * whether the transform was changed.</p>
    241      *
    242      * @return Whether {@code mAutoAdjustTransform}.
    243      */
    244     private boolean updateTransform() {
    245         Log.v(TAG, "updateTransform");
    246         if (!mAutoAdjustTransform) {
    247             return false;
    248         }
    249         if (mAspectRatio == MATCH_SCREEN || mAspectRatio < 0 || mWidth == 0 || mHeight == 0) {
    250             return true;
    251         }
    252 
    253         Matrix matrix;
    254         int cameraId = mCameraProvider.getCurrentCameraId();
    255         if (cameraId >= 0) {
    256             CameraDeviceInfo.Characteristics info = mCameraProvider.getCharacteristics(cameraId);
    257             matrix = info.getPreviewTransform(mOrientation, new RectF(0, 0, mWidth, mHeight),
    258                     mCaptureLayoutHelper.getPreviewRect());
    259         } else {
    260             Log.w(TAG, "Unable to find current camera... defaulting to identity matrix");
    261             matrix = new Matrix();
    262         }
    263 
    264         mPreview.setTransform(matrix);
    265         updatePreviewArea(matrix);
    266         return true;
    267     }
    268 
    269     private void onPreviewAreaChanged(final RectF previewArea) {
    270         // Notify listeners of preview area change
    271         final List<PreviewStatusListener.PreviewAreaChangedListener> listeners =
    272                 new ArrayList<PreviewStatusListener.PreviewAreaChangedListener>(
    273                         mPreviewSizeChangedListeners);
    274         // This method can be called during layout pass. We post a Runnable so
    275         // that the callbacks won't happen during the layout pass.
    276         mPreview.post(new Runnable() {
    277             @Override
    278             public void run() {
    279                 for (PreviewStatusListener.PreviewAreaChangedListener listener : listeners) {
    280                     listener.onPreviewAreaChanged(previewArea);
    281                 }
    282             }
    283         });
    284     }
    285 
    286     /**
    287      * Returns a new copy of the preview area, to avoid internal data being modified
    288      * from outside of the class.
    289      */
    290     public RectF getPreviewArea() {
    291         return new RectF(mPreviewArea);
    292     }
    293 
    294     /**
    295      * Returns a copy of the area of the whole preview, including bits clipped
    296      * by the view
    297      */
    298     public RectF getTextureArea() {
    299 
    300         if (mPreview == null) {
    301             return new RectF();
    302         }
    303         Matrix matrix = new Matrix();
    304         RectF area = new RectF(0, 0, mWidth, mHeight);
    305         mPreview.getTransform(matrix).mapRect(area);
    306         return area;
    307     }
    308 
    309     public Bitmap getPreviewBitmap(int downsample) {
    310         RectF textureArea = getTextureArea();
    311         int width = (int) textureArea.width() / downsample;
    312         int height = (int) textureArea.height() / downsample;
    313         Bitmap preview = mPreview.getBitmap(width, height);
    314         return Bitmap.createBitmap(preview, 0, 0, width, height, mPreview.getTransform(null), true);
    315     }
    316 
    317     /**
    318      * Adds a listener that will get notified when the preview area changed. This
    319      * can be useful for UI elements or focus overlay to adjust themselves according
    320      * to the preview area change.
    321      * <p/>
    322      * Note that a listener will only be added once. A newly added listener will receive
    323      * a notification of current preview area immediately after being added.
    324      * <p/>
    325      * This function should be called on the UI thread and listeners will be notified
    326      * on the UI thread.
    327      *
    328      * @param listener the listener that will get notified of preview area change
    329      */
    330     public void addPreviewAreaSizeChangedListener(
    331             PreviewStatusListener.PreviewAreaChangedListener listener) {
    332         if (listener != null && !mPreviewSizeChangedListeners.contains(listener)) {
    333             mPreviewSizeChangedListeners.add(listener);
    334             if (mPreviewArea.width() == 0 || mPreviewArea.height() == 0) {
    335                 listener.onPreviewAreaChanged(new RectF(0, 0, mWidth, mHeight));
    336             } else {
    337                 listener.onPreviewAreaChanged(new RectF(mPreviewArea));
    338             }
    339         }
    340     }
    341 
    342     /**
    343      * Removes a listener that gets notified when the preview area changed.
    344      *
    345      * @param listener the listener that gets notified of preview area change
    346      */
    347     public void removePreviewAreaSizeChangedListener(
    348             PreviewStatusListener.PreviewAreaChangedListener listener) {
    349         if (listener != null && mPreviewSizeChangedListeners.contains(listener)) {
    350             mPreviewSizeChangedListeners.remove(listener);
    351         }
    352     }
    353 
    354     @Override
    355     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    356         // Workaround for b/11168275, see b/10981460 for more details
    357         if (mWidth != 0 && mHeight != 0) {
    358             // Re-apply transform matrix for new surface texture
    359             updateTransform();
    360         }
    361         if (mSurfaceTextureListener != null) {
    362             mSurfaceTextureListener.onSurfaceTextureAvailable(surface, width, height);
    363         }
    364     }
    365 
    366     @Override
    367     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    368         if (mSurfaceTextureListener != null) {
    369             mSurfaceTextureListener.onSurfaceTextureSizeChanged(surface, width, height);
    370         }
    371     }
    372 
    373     @Override
    374     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    375         if (mSurfaceTextureListener != null) {
    376             mSurfaceTextureListener.onSurfaceTextureDestroyed(surface);
    377         }
    378         return false;
    379     }
    380 
    381     @Override
    382     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    383         if (mSurfaceTextureListener != null) {
    384             mSurfaceTextureListener.onSurfaceTextureUpdated(surface);
    385         }
    386 
    387     }
    388 }
    389