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 91 public GestureDetector.OnGestureListener getGestureListener() { 92 return mPreviewGestureListener; 93 } 94 95 @Override 96 public View.OnTouchListener getTouchListener() { 97 return null; 98 } 99 100 @Override 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 112 public boolean shouldAutoAdjustTransformMatrixOnLayout() { 113 return true; 114 } 115 116 @Override 117 public void onPreviewFlipped() { 118 mController.updateCameraOrientation(); 119 } 120 121 /** 122 * Starts the countdown timer. 123 * 124 * @param sec seconds to countdown 125 */ 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 */ 133 public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) { 134 mCountdownView.setCountDownStatusListener(listener); 135 } 136 137 /** 138 * Returns whether the countdown is on-going. 139 */ 140 public boolean isCountingDown() { 141 return mCountdownView.isCountingDown(); 142 } 143 144 /** 145 * Cancels the on-going countdown, if any. 146 */ 147 public void cancelCountDown() { 148 mCountdownView.cancelCountDown(); 149 } 150 151 @Override 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 164 public DecodeTask(byte[] data, int orientation, boolean mirror) { 165 mData = data; 166 mOrientation = orientation; 167 mMirror = mirror; 168 } 169 170 @Override 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 { 178 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) { 179 super(data, orientation, mirror); 180 } 181 182 @Override 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 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 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 */ 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 */ 247 public void hideIntentReviewImageView() { 248 if (mIntentReviewImageView != null) { 249 mIntentReviewImageView.setVisibility(View.INVISIBLE); 250 } 251 } 252 253 254 public FocusRing getFocusRing() { 255 return mFocusRing; 256 } 257 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 275 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 276 mController.onPreviewUIReady(); 277 } 278 279 @Override 280 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 281 // Ignored, Camera does all the work for us 282 } 283 284 @Override 285 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 286 mController.onPreviewUIDestroyed(); 287 return true; 288 } 289 290 @Override 291 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 292 } 293 294 private void initIndicators() { 295 // TODO init toggle buttons on bottom bar here 296 } 297 298 public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) { 299 initializeZoom(capabilities, settings); 300 } 301 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 309 public void initializeFirstTime() { 310 311 } 312 313 // called from onResume every other time 314 public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) { 315 initializeZoom(capabilities, settings); 316 if (mController.isImageCaptureIntent()) { 317 hidePostCaptureAlert(); 318 } 319 } 320 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 334 public void animateFlash() { 335 mController.startPreCaptureAnimation(); 336 } 337 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 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 361 protected void hidePostCaptureAlert() { 362 if (mDecodeTaskForReview != null) { 363 mDecodeTaskForReview.cancel(true); 364 } 365 resumeFaceDetection(); 366 } 367 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 376 public void onZoomValueChanged(float ratio) { 377 mController.onZoomChanged(ratio); 378 } 379 380 @Override 381 public void onZoomStart() { 382 } 383 384 @Override 385 public void onZoomEnd() { 386 } 387 } 388 389 public void setSwipingEnabled(boolean enable) { 390 mActivity.setSwipingEnabled(enable); 391 } 392 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 404 public void clearFaces() { 405 if (mFaceView != null) { 406 mFaceView.clear(); 407 } 408 } 409 410 public void pauseFaceDetection() { 411 if (mFaceView != null) { 412 mFaceView.pause(); 413 } 414 } 415 416 public void resumeFaceDetection() { 417 if (mFaceView != null) { 418 mFaceView.resume(); 419 } 420 } 421 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 433 public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) { 434 if (mFaceView != null) { 435 mFaceView.setFaces(faces); 436 } 437 } 438 439 } 440