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 TextureViewHelper(TextureView preview, CaptureLayoutHelper helper, CameraProvider cameraProvider)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 */ setAutoAdjustTransform(boolean enable)88 public void setAutoAdjustTransform(boolean enable) { 89 mAutoAdjustTransform = enable; 90 } 91 92 @Override onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)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 */ clearTransform()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 updateAspectRatio(float aspectRatio)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 setAspectRatio(float aspectRatio)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 onAspectRatioChanged()147 private void onAspectRatioChanged() { 148 mCaptureLayoutHelper.onPreviewAspectRatioChanged(mAspectRatio); 149 for (PreviewStatusListener.PreviewAspectRatioChangedListener listener 150 : mAspectRatioChangedListeners) { 151 listener.onPreviewAspectRatioChanged(mAspectRatio); 152 } 153 } 154 addAspectRatioChangedListener( PreviewStatusListener.PreviewAspectRatioChangedListener listener)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 */ getFullscreenRect()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 */ updateTransformFullScreen(Matrix matrix, float aspectRatio)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