PhotoUI.java revision 5f828150c228d69fdeafb06609918073d5be47f5
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 17package com.android.camera; 18 19import android.app.Dialog; 20import android.content.DialogInterface; 21import android.graphics.Bitmap; 22import android.graphics.Color; 23import android.graphics.Matrix; 24import android.graphics.RectF; 25import android.graphics.SurfaceTexture; 26import android.hardware.Camera.Face; 27import android.os.AsyncTask; 28import android.os.Build; 29import android.view.GestureDetector; 30import android.view.MotionEvent; 31import android.view.View; 32import android.view.ViewGroup; 33import android.widget.FrameLayout; 34import android.widget.ImageView; 35 36import com.android.camera.FocusOverlayManager.FocusUI; 37import com.android.camera.debug.Log; 38import com.android.camera.ui.CountDownView; 39import com.android.camera.ui.FaceView; 40import com.android.camera.ui.PreviewOverlay; 41import com.android.camera.ui.PreviewStatusListener; 42import com.android.camera.util.CameraUtil; 43import com.android.camera.widget.AspectRatioDialogLayout; 44import com.android.camera.widget.AspectRatioSelector; 45import com.android.camera.widget.LocationDialogLayout; 46import com.android.camera2.R; 47import com.android.ex.camera2.portability.CameraCapabilities; 48import com.android.ex.camera2.portability.CameraAgent; 49import com.android.ex.camera2.portability.CameraSettings; 50 51import java.util.List; 52 53public class PhotoUI implements PreviewStatusListener, 54 CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener { 55 56 private static final Log.Tag TAG = new Log.Tag("PhotoUI"); 57 private static final int DOWN_SAMPLE_FACTOR = 4; 58 private static final float UNSET = 0f; 59 60 private final PreviewOverlay mPreviewOverlay; 61 private final FocusUI mFocusUI; 62 private final CameraActivity mActivity; 63 private final PhotoController mController; 64 65 private final View mRootView; 66 private Dialog mDialog = null; 67 68 // TODO: Remove face view logic if UX does not bring it back within a month. 69 private final FaceView mFaceView = null; 70 private DecodeImageForReview mDecodeTaskForReview = null; 71 72 private int mZoomMax; 73 private List<Integer> mZoomRatios; 74 75 private int mPreviewWidth = 0; 76 private int mPreviewHeight = 0; 77 private float mAspectRatio = UNSET; 78 79 private ImageView mIntentReviewImageView; 80 81 private final GestureDetector.OnGestureListener mPreviewGestureListener 82 = new GestureDetector.SimpleOnGestureListener() { 83 @Override 84 public boolean onSingleTapUp(MotionEvent ev) { 85 mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY()); 86 return true; 87 } 88 }; 89 private final DialogInterface.OnDismissListener mOnDismissListener 90 = new DialogInterface.OnDismissListener() { 91 @Override 92 public void onDismiss(DialogInterface dialog) { 93 mDialog = null; 94 } 95 }; 96 private Runnable mRunnableForNextFrame = null; 97 private CountDownView mCountdownView; 98 99 @Override 100 public GestureDetector.OnGestureListener getGestureListener() { 101 return mPreviewGestureListener; 102 } 103 104 @Override 105 public View.OnTouchListener getTouchListener() { 106 return null; 107 } 108 109 @Override 110 public void onPreviewLayoutChanged(View v, int left, int top, int right, 111 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 112 int width = right - left; 113 int height = bottom - top; 114 if (mPreviewWidth != width || mPreviewHeight != height) { 115 mPreviewWidth = width; 116 mPreviewHeight = height; 117 } 118 } 119 120 @Override 121 public boolean shouldAutoAdjustTransformMatrixOnLayout() { 122 return true; 123 } 124 125 @Override 126 public boolean shouldAutoAdjustBottomBar() { 127 return true; 128 } 129 130 @Override 131 public void onPreviewFlipped() { 132 mController.updateCameraOrientation(); 133 } 134 135 /** 136 * Sets the runnable to run when the next frame comes in. 137 */ 138 public void setRunnableForNextFrame(Runnable runnable) { 139 mRunnableForNextFrame = runnable; 140 } 141 142 /** 143 * Starts the countdown timer. 144 * 145 * @param sec seconds to countdown 146 */ 147 public void startCountdown(int sec) { 148 mCountdownView.startCountDown(sec); 149 } 150 151 /** 152 * Sets a listener that gets notified when the countdown is finished. 153 */ 154 public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) { 155 mCountdownView.setCountDownStatusListener(listener); 156 } 157 158 /** 159 * Returns whether the countdown is on-going. 160 */ 161 public boolean isCountingDown() { 162 return mCountdownView.isCountingDown(); 163 } 164 165 /** 166 * Cancels the on-going countdown, if any. 167 */ 168 public void cancelCountDown() { 169 mCountdownView.cancelCountDown(); 170 } 171 172 @Override 173 public void onPreviewAreaChanged(RectF previewArea) { 174 if (mFaceView != null) { 175 mFaceView.onPreviewAreaChanged(previewArea); 176 } 177 mCountdownView.onPreviewAreaChanged(previewArea); 178 } 179 180 private class DecodeTask extends AsyncTask<Void, Void, Bitmap> { 181 private final byte [] mData; 182 private final int mOrientation; 183 private final boolean mMirror; 184 185 public DecodeTask(byte[] data, int orientation, boolean mirror) { 186 mData = data; 187 mOrientation = orientation; 188 mMirror = mirror; 189 } 190 191 @Override 192 protected Bitmap doInBackground(Void... params) { 193 // Decode image in background. 194 Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR); 195 if (mOrientation != 0 || mMirror) { 196 Matrix m = new Matrix(); 197 if (mMirror) { 198 // Flip horizontally 199 m.setScale(-1f, 1f); 200 } 201 m.preRotate(mOrientation); 202 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, 203 false); 204 } 205 return bitmap; 206 } 207 } 208 209 private class DecodeImageForReview extends DecodeTask { 210 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) { 211 super(data, orientation, mirror); 212 } 213 214 @Override 215 protected void onPostExecute(Bitmap bitmap) { 216 if (isCancelled()) { 217 return; 218 } 219 220 mIntentReviewImageView.setImageBitmap(bitmap); 221 showIntentReviewImageView(); 222 223 mDecodeTaskForReview = null; 224 } 225 } 226 227 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { 228 mActivity = activity; 229 mController = controller; 230 mRootView = parent; 231 232 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); 233 mActivity.getLayoutInflater().inflate(R.layout.photo_module, 234 moduleRoot, true); 235 initIndicators(); 236 mFocusUI = (FocusUI) mRootView.findViewById(R.id.focus_overlay); 237 mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay); 238 mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view); 239 240 if (mController.isImageCaptureIntent()) { 241 initIntentReviewImageView(); 242 } 243 } 244 245 private void initIntentReviewImageView() { 246 mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview); 247 mActivity.getCameraAppUI().addPreviewAreaChangedListener( 248 new PreviewStatusListener.PreviewAreaChangedListener() { 249 @Override 250 public void onPreviewAreaChanged(RectF previewArea) { 251 FrameLayout.LayoutParams params = 252 (FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams(); 253 params.width = (int) previewArea.width(); 254 params.height = (int) previewArea.height(); 255 params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0); 256 mIntentReviewImageView.setLayoutParams(params); 257 } 258 }); 259 } 260 261 /** 262 * Show the image review over the live preview for intent captures. 263 */ 264 public void showIntentReviewImageView() { 265 if (mIntentReviewImageView != null) { 266 mIntentReviewImageView.setVisibility(View.VISIBLE); 267 } 268 } 269 270 /** 271 * Hide the image review over the live preview for intent captures. 272 */ 273 public void hideIntentReviewImageView() { 274 if (mIntentReviewImageView != null) { 275 mIntentReviewImageView.setVisibility(View.INVISIBLE); 276 } 277 } 278 279 280 public FocusUI getFocusUI() { 281 return mFocusUI; 282 } 283 284 public void updatePreviewAspectRatio(float aspectRatio) { 285 if (aspectRatio <= 0) { 286 Log.e(TAG, "Invalid aspect ratio: " + aspectRatio); 287 return; 288 } 289 if (aspectRatio < 1f) { 290 aspectRatio = 1f / aspectRatio; 291 } 292 293 if (mAspectRatio != aspectRatio) { 294 mAspectRatio = aspectRatio; 295 // Update transform matrix with the new aspect ratio. 296 mController.updatePreviewAspectRatio(mAspectRatio); 297 } 298 } 299 300 @Override 301 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 302 mController.onPreviewUIReady(); 303 } 304 305 @Override 306 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 307 // Ignored, Camera does all the work for us 308 } 309 310 @Override 311 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 312 mController.onPreviewUIDestroyed(); 313 return true; 314 } 315 316 @Override 317 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 318 if (mRunnableForNextFrame != null) { 319 mRootView.post(mRunnableForNextFrame); 320 mRunnableForNextFrame = null; 321 } 322 } 323 324 public View getRootView() { 325 return mRootView; 326 } 327 328 private void initIndicators() { 329 // TODO init toggle buttons on bottom bar here 330 } 331 332 public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) { 333 initializeZoom(capabilities, settings); 334 } 335 336 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) { 337 // Decode jpeg byte array and then animate the jpeg 338 DecodeTask task = new DecodeTask(jpegData, orientation, mirror); 339 task.execute(); 340 } 341 342 // called from onResume but only the first time 343 public void initializeFirstTime() { 344 345 } 346 347 // called from onResume every other time 348 public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) { 349 initializeZoom(capabilities, settings); 350 if (mController.isImageCaptureIntent()) { 351 hidePostCaptureAlert(); 352 } 353 } 354 355 public void showLocationAndAspectRatioDialog( 356 final PhotoModule.LocationDialogCallback locationCallback, 357 final PhotoModule.AspectRatioDialogCallback aspectRatioDialogCallback) { 358 setDialog(new Dialog(mActivity, 359 android.R.style.Theme_Black_NoTitleBar_Fullscreen)); 360 final LocationDialogLayout locationDialogLayout = (LocationDialogLayout) mActivity 361 .getLayoutInflater().inflate(R.layout.location_dialog_layout, null); 362 locationDialogLayout.setLocationTaggingSelectionListener( 363 new LocationDialogLayout.LocationTaggingSelectionListener() { 364 @Override 365 public void onLocationTaggingSelected(boolean selected) { 366 // Update setting. 367 locationCallback.onLocationTaggingSelected(selected); 368 369 if (showAspectRatioDialogOnThisDevice()) { 370 // Go to next page. 371 showAspectRatioDialog(aspectRatioDialogCallback, mDialog); 372 } else { 373 // If we don't want to show the aspect ratio dialog, 374 // dismiss the dialog right after the user chose the 375 // location setting. 376 if (mDialog != null) { 377 mDialog.dismiss(); 378 } 379 } 380 } 381 }); 382 mDialog.setContentView(locationDialogLayout, new ViewGroup.LayoutParams( 383 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 384 mDialog.show(); 385 } 386 387 /** 388 * Dismisses previous dialog if any, sets current dialog to the given dialog, 389 * and set the on dismiss listener for the given dialog. 390 * @param dialog dialog to show 391 */ 392 private void setDialog(Dialog dialog) { 393 if (mDialog != null) { 394 mDialog.setOnDismissListener(null); 395 mDialog.dismiss(); 396 } 397 mDialog = dialog; 398 if (mDialog != null) { 399 mDialog.setOnDismissListener(mOnDismissListener); 400 } 401 } 402 403 public void showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback) { 404 if (showAspectRatioDialogOnThisDevice()) { 405 setDialog(new Dialog(mActivity, android.R.style.Theme_Black_NoTitleBar_Fullscreen)); 406 showAspectRatioDialog(callback, mDialog); 407 } 408 } 409 410 private void showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback, 411 final Dialog aspectRatioDialog) { 412 if (aspectRatioDialog == null) { 413 Log.e(TAG, "Dialog for aspect ratio is null."); 414 return; 415 } 416 final AspectRatioDialogLayout aspectRatioDialogLayout = 417 (AspectRatioDialogLayout) mActivity 418 .getLayoutInflater().inflate(R.layout.aspect_ratio_dialog_layout, null); 419 aspectRatioDialogLayout.initialize( 420 new AspectRatioDialogLayout.AspectRatioChangedListener() { 421 @Override 422 public void onAspectRatioChanged(AspectRatioSelector.AspectRatio aspectRatio) { 423 // callback to set picture size. 424 callback.onAspectRatioSelected(aspectRatio, new Runnable() { 425 @Override 426 public void run() { 427 if (mDialog != null) { 428 mDialog.dismiss(); 429 } 430 } 431 }); 432 } 433 }, callback.getCurrentAspectRatio()); 434 aspectRatioDialog.setContentView(aspectRatioDialogLayout, new ViewGroup.LayoutParams( 435 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 436 aspectRatioDialog.show(); 437 } 438 439 /** 440 * @return Whether this is a device that we should show the aspect ratio 441 * intro dialog on. 442 */ 443 private boolean showAspectRatioDialogOnThisDevice() { 444 // We only want to show that dialog on N4 and N5 445 return "hammerhead".equals(Build.DEVICE) || "mako".equals(Build.DEVICE); 446 } 447 448 public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) { 449 if ((capabilities == null) || settings == null || 450 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) { 451 return; 452 } 453 mZoomMax = capabilities.getMaxZoomIndex(); 454 mZoomRatios = capabilities.getZoomRatioList(); 455 // Currently we use immediate zoom for fast zooming to get better UX and 456 // there is no plan to take advantage of the smooth zoom. 457 // TODO: Need to setup a path to AppUI to do this 458 mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomIndex(), mZoomRatios, 459 new ZoomChangeListener()); 460 } 461 462 public void animateFlash() { 463 mController.startPreCaptureAnimation(); 464 } 465 466 public boolean onBackPressed() { 467 // In image capture mode, back button should: 468 // 1) if there is any popup, dismiss them, 2) otherwise, get out of 469 // image capture 470 if (mController.isImageCaptureIntent()) { 471 mController.onCaptureCancelled(); 472 return true; 473 } else if (!mController.isCameraIdle()) { 474 // ignore backs while we're taking a picture 475 return true; 476 } else { 477 return false; 478 } 479 } 480 481 protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) { 482 mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror); 483 mDecodeTaskForReview.execute(); 484 485 mActivity.getCameraAppUI().transitionToIntentReviewLayout(); 486 pauseFaceDetection(); 487 } 488 489 protected void hidePostCaptureAlert() { 490 if (mDecodeTaskForReview != null) { 491 mDecodeTaskForReview.cancel(true); 492 } 493 resumeFaceDetection(); 494 } 495 496 public void setDisplayOrientation(int orientation) { 497 if (mFaceView != null) { 498 mFaceView.setDisplayOrientation(orientation); 499 } 500 } 501 502 private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener { 503 @Override 504 public void onZoomValueChanged(int index) { 505 mController.onZoomChanged(index); 506 } 507 508 @Override 509 public void onZoomStart() { 510 } 511 512 @Override 513 public void onZoomEnd() { 514 } 515 } 516 517 public void setSwipingEnabled(boolean enable) { 518 mActivity.setSwipingEnabled(enable); 519 } 520 521 public void onPause() { 522 if (mFaceView != null) { 523 mFaceView.clear(); 524 } 525 if (mDialog != null) { 526 mDialog.dismiss(); 527 } 528 } 529 530 public void clearFaces() { 531 if (mFaceView != null) { 532 mFaceView.clear(); 533 } 534 } 535 536 public void pauseFaceDetection() { 537 if (mFaceView != null) { 538 mFaceView.pause(); 539 } 540 } 541 542 public void resumeFaceDetection() { 543 if (mFaceView != null) { 544 mFaceView.resume(); 545 } 546 } 547 548 public void onStartFaceDetection(int orientation, boolean mirror) { 549 if (mFaceView != null) { 550 mFaceView.clear(); 551 mFaceView.setVisibility(View.VISIBLE); 552 mFaceView.setDisplayOrientation(orientation); 553 mFaceView.setMirror(mirror); 554 mFaceView.resume(); 555 } 556 } 557 558 @Override 559 public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) { 560 if (mFaceView != null) { 561 mFaceView.setFaces(faces); 562 } 563 } 564 565} 566