1 /* 2 * Copyright (C) 2012 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.app.Dialog; 20 import android.content.DialogInterface; 21 import android.graphics.Bitmap; 22 import android.graphics.RectF; 23 import android.graphics.SurfaceTexture; 24 import android.hardware.Camera.Face; 25 import android.os.AsyncTask; 26 import android.view.GestureDetector; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.FrameLayout; 31 import android.widget.ImageView; 32 33 import com.android.camera.captureintent.PictureDecoder; 34 import com.android.camera.debug.DebugPropertyHelper; 35 import com.android.camera.debug.Log; 36 import com.android.camera.ui.CountDownView; 37 import com.android.camera.ui.FaceView; 38 import com.android.camera.ui.PreviewOverlay; 39 import com.android.camera.ui.PreviewStatusListener; 40 import com.android.camera.ui.focus.FocusRing; 41 import com.android.camera2.R; 42 import com.android.ex.camera2.portability.CameraAgent; 43 import com.android.ex.camera2.portability.CameraCapabilities; 44 import com.android.ex.camera2.portability.CameraSettings; 45 46 public class PhotoUI implements PreviewStatusListener, 47 CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener { 48 49 private static final Log.Tag TAG = new Log.Tag("PhotoUI"); 50 private static final int DOWN_SAMPLE_FACTOR = 4; 51 private static final float UNSET = 0f; 52 53 private final PreviewOverlay mPreviewOverlay; 54 private final FocusRing mFocusRing; 55 private final CameraActivity mActivity; 56 private final PhotoController mController; 57 58 private final View mRootView; 59 private Dialog mDialog = null; 60 61 // TODO: Remove face view logic if UX does not bring it back within a month. 62 private final FaceView mFaceView; 63 private DecodeImageForReview mDecodeTaskForReview = null; 64 65 private float mZoomMax; 66 67 private int mPreviewWidth = 0; 68 private int mPreviewHeight = 0; 69 private float mAspectRatio = UNSET; 70 71 private ImageView mIntentReviewImageView; 72 73 private final GestureDetector.OnGestureListener mPreviewGestureListener 74 = new GestureDetector.SimpleOnGestureListener() { 75 @Override 76 public boolean onSingleTapUp(MotionEvent ev) { 77 mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY()); 78 return true; 79 } 80 }; 81 private final DialogInterface.OnDismissListener mOnDismissListener 82 = new DialogInterface.OnDismissListener() { 83 @Override 84 public void onDismiss(DialogInterface dialog) { 85 mDialog = null; 86 } 87 }; 88 private final CountDownView mCountdownView; 89 90 @Override getGestureListener()91 public GestureDetector.OnGestureListener getGestureListener() { 92 return mPreviewGestureListener; 93 } 94 95 @Override getTouchListener()96 public View.OnTouchListener getTouchListener() { 97 return null; 98 } 99 100 @Override onPreviewLayoutChanged(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)101 public void onPreviewLayoutChanged(View v, int left, int top, int right, 102 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 103 int width = right - left; 104 int height = bottom - top; 105 if (mPreviewWidth != width || mPreviewHeight != height) { 106 mPreviewWidth = width; 107 mPreviewHeight = height; 108 } 109 } 110 111 @Override shouldAutoAdjustTransformMatrixOnLayout()112 public boolean shouldAutoAdjustTransformMatrixOnLayout() { 113 return true; 114 } 115 116 @Override onPreviewFlipped()117 public void onPreviewFlipped() { 118 mController.updateCameraOrientation(); 119 } 120 121 /** 122 * Starts the countdown timer. 123 * 124 * @param sec seconds to countdown 125 */ startCountdown(int sec)126 public void startCountdown(int sec) { 127 mCountdownView.startCountDown(sec); 128 } 129 130 /** 131 * Sets a listener that gets notified when the countdown is finished. 132 */ setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener)133 public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) { 134 mCountdownView.setCountDownStatusListener(listener); 135 } 136 137 /** 138 * Returns whether the countdown is on-going. 139 */ isCountingDown()140 public boolean isCountingDown() { 141 return mCountdownView.isCountingDown(); 142 } 143 144 /** 145 * Cancels the on-going countdown, if any. 146 */ cancelCountDown()147 public void cancelCountDown() { 148 mCountdownView.cancelCountDown(); 149 } 150 151 @Override onPreviewAreaChanged(RectF previewArea)152 public void onPreviewAreaChanged(RectF previewArea) { 153 if (mFaceView != null) { 154 mFaceView.onPreviewAreaChanged(previewArea); 155 } 156 mCountdownView.onPreviewAreaChanged(previewArea); 157 } 158 159 private class DecodeTask extends AsyncTask<Void, Void, Bitmap> { 160 private final byte [] mData; 161 private final int mOrientation; 162 private final boolean mMirror; 163 DecodeTask(byte[] data, int orientation, boolean mirror)164 public DecodeTask(byte[] data, int orientation, boolean mirror) { 165 mData = data; 166 mOrientation = orientation; 167 mMirror = mirror; 168 } 169 170 @Override doInBackground(Void... params)171 protected Bitmap doInBackground(Void... params) { 172 // Decode image in background. 173 return PictureDecoder.decode(mData, DOWN_SAMPLE_FACTOR, mOrientation, mMirror); 174 } 175 } 176 177 private class DecodeImageForReview extends DecodeTask { DecodeImageForReview(byte[] data, int orientation, boolean mirror)178 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) { 179 super(data, orientation, mirror); 180 } 181 182 @Override onPostExecute(Bitmap bitmap)183 protected void onPostExecute(Bitmap bitmap) { 184 if (isCancelled()) { 185 return; 186 } 187 188 mIntentReviewImageView.setImageBitmap(bitmap); 189 showIntentReviewImageView(); 190 191 mDecodeTaskForReview = null; 192 } 193 } 194 PhotoUI(CameraActivity activity, PhotoController controller, View parent)195 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { 196 mActivity = activity; 197 mController = controller; 198 mRootView = parent; 199 200 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); 201 mActivity.getLayoutInflater().inflate(R.layout.photo_module, 202 moduleRoot, true); 203 initIndicators(); 204 mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring); 205 mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay); 206 mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view); 207 // Show faces if we are in debug mode. 208 if (DebugPropertyHelper.showCaptureDebugUI()) { 209 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view); 210 } else { 211 mFaceView = null; 212 } 213 214 if (mController.isImageCaptureIntent()) { 215 initIntentReviewImageView(); 216 } 217 } 218 initIntentReviewImageView()219 private void initIntentReviewImageView() { 220 mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview); 221 mActivity.getCameraAppUI().addPreviewAreaChangedListener( 222 new PreviewStatusListener.PreviewAreaChangedListener() { 223 @Override 224 public void onPreviewAreaChanged(RectF previewArea) { 225 FrameLayout.LayoutParams params = 226 (FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams(); 227 params.width = (int) previewArea.width(); 228 params.height = (int) previewArea.height(); 229 params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0); 230 mIntentReviewImageView.setLayoutParams(params); 231 } 232 }); 233 } 234 235 /** 236 * Show the image review over the live preview for intent captures. 237 */ showIntentReviewImageView()238 public void showIntentReviewImageView() { 239 if (mIntentReviewImageView != null) { 240 mIntentReviewImageView.setVisibility(View.VISIBLE); 241 } 242 } 243 244 /** 245 * Hide the image review over the live preview for intent captures. 246 */ hideIntentReviewImageView()247 public void hideIntentReviewImageView() { 248 if (mIntentReviewImageView != null) { 249 mIntentReviewImageView.setVisibility(View.INVISIBLE); 250 } 251 } 252 253 getFocusRing()254 public FocusRing getFocusRing() { 255 return mFocusRing; 256 } 257 updatePreviewAspectRatio(float aspectRatio)258 public void updatePreviewAspectRatio(float aspectRatio) { 259 if (aspectRatio <= 0) { 260 Log.e(TAG, "Invalid aspect ratio: " + aspectRatio); 261 return; 262 } 263 if (aspectRatio < 1f) { 264 aspectRatio = 1f / aspectRatio; 265 } 266 267 if (mAspectRatio != aspectRatio) { 268 mAspectRatio = aspectRatio; 269 // Update transform matrix with the new aspect ratio. 270 mController.updatePreviewAspectRatio(mAspectRatio); 271 } 272 } 273 274 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)275 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 276 mController.onPreviewUIReady(); 277 } 278 279 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)280 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 281 // Ignored, Camera does all the work for us 282 } 283 284 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)285 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 286 mController.onPreviewUIDestroyed(); 287 return true; 288 } 289 290 @Override onSurfaceTextureUpdated(SurfaceTexture surface)291 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 292 } 293 initIndicators()294 private void initIndicators() { 295 // TODO init toggle buttons on bottom bar here 296 } 297 onCameraOpened(CameraCapabilities capabilities, CameraSettings settings)298 public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) { 299 initializeZoom(capabilities, settings); 300 } 301 animateCapture(final byte[] jpegData, int orientation, boolean mirror)302 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) { 303 // Decode jpeg byte array and then animate the jpeg 304 DecodeTask task = new DecodeTask(jpegData, orientation, mirror); 305 task.execute(); 306 } 307 308 // called from onResume but only the first time initializeFirstTime()309 public void initializeFirstTime() { 310 311 } 312 313 // called from onResume every other time initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings)314 public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) { 315 initializeZoom(capabilities, settings); 316 if (mController.isImageCaptureIntent()) { 317 hidePostCaptureAlert(); 318 } 319 } 320 initializeZoom(CameraCapabilities capabilities, CameraSettings settings)321 public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) { 322 if ((capabilities == null) || settings == null || 323 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) { 324 return; 325 } 326 mZoomMax = capabilities.getMaxZoomRatio(); 327 // Currently we use immediate zoom for fast zooming to get better UX and 328 // there is no plan to take advantage of the smooth zoom. 329 // TODO: Need to setup a path to AppUI to do this 330 mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomRatio(), 331 new ZoomChangeListener()); 332 } 333 animateFlash()334 public void animateFlash() { 335 mController.startPreCaptureAnimation(); 336 } 337 onBackPressed()338 public boolean onBackPressed() { 339 // In image capture mode, back button should: 340 // 1) if there is any popup, dismiss them, 2) otherwise, get out of 341 // image capture 342 if (mController.isImageCaptureIntent()) { 343 mController.onCaptureCancelled(); 344 return true; 345 } else if (!mController.isCameraIdle()) { 346 // ignore backs while we're taking a picture 347 return true; 348 } else { 349 return false; 350 } 351 } 352 showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror)353 protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) { 354 mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror); 355 mDecodeTaskForReview.execute(); 356 357 mActivity.getCameraAppUI().transitionToIntentReviewLayout(); 358 pauseFaceDetection(); 359 } 360 hidePostCaptureAlert()361 protected void hidePostCaptureAlert() { 362 if (mDecodeTaskForReview != null) { 363 mDecodeTaskForReview.cancel(true); 364 } 365 resumeFaceDetection(); 366 } 367 setDisplayOrientation(int orientation)368 public void setDisplayOrientation(int orientation) { 369 if (mFaceView != null) { 370 mFaceView.setDisplayOrientation(orientation); 371 } 372 } 373 374 private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener { 375 @Override onZoomValueChanged(float ratio)376 public void onZoomValueChanged(float ratio) { 377 mController.onZoomChanged(ratio); 378 } 379 380 @Override onZoomStart()381 public void onZoomStart() { 382 } 383 384 @Override onZoomEnd()385 public void onZoomEnd() { 386 } 387 } 388 setSwipingEnabled(boolean enable)389 public void setSwipingEnabled(boolean enable) { 390 mActivity.setSwipingEnabled(enable); 391 } 392 onPause()393 public void onPause() { 394 if (mFaceView != null) { 395 mFaceView.clear(); 396 } 397 if (mDialog != null) { 398 mDialog.dismiss(); 399 } 400 // recalculate aspect ratio when restarting. 401 mAspectRatio = 0.0f; 402 } 403 clearFaces()404 public void clearFaces() { 405 if (mFaceView != null) { 406 mFaceView.clear(); 407 } 408 } 409 pauseFaceDetection()410 public void pauseFaceDetection() { 411 if (mFaceView != null) { 412 mFaceView.pause(); 413 } 414 } 415 resumeFaceDetection()416 public void resumeFaceDetection() { 417 if (mFaceView != null) { 418 mFaceView.resume(); 419 } 420 } 421 onStartFaceDetection(int orientation, boolean mirror)422 public void onStartFaceDetection(int orientation, boolean mirror) { 423 if (mFaceView != null) { 424 mFaceView.clear(); 425 mFaceView.setVisibility(View.VISIBLE); 426 mFaceView.setDisplayOrientation(orientation); 427 mFaceView.setMirror(mirror); 428 mFaceView.resume(); 429 } 430 } 431 432 @Override onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera)433 public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) { 434 if (mFaceView != null) { 435 mFaceView.setFaces(faces); 436 } 437 } 438 439 } 440