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