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