PhotoUI.java revision 4efa8b54c1df4e06f2d3caed2568015a737f9dda
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.AlertDialog; 20import android.graphics.Bitmap; 21import android.graphics.Matrix; 22import android.graphics.SurfaceTexture; 23import android.hardware.Camera; 24import android.hardware.Camera.Face; 25import android.os.AsyncTask; 26import android.util.Log; 27import android.view.GestureDetector; 28import android.view.MotionEvent; 29import android.view.TextureView; 30import android.view.View; 31import android.view.View.OnClickListener; 32import android.view.ViewGroup; 33import android.view.ViewStub; 34import android.widget.Toast; 35 36import com.android.camera.FocusOverlayManager.FocusUI; 37import com.android.camera.app.CameraManager; 38import com.android.camera.settings.SettingsManager; 39import com.android.camera.ui.FaceView; 40import com.android.camera.ui.FocusIndicator; 41import com.android.camera.ui.ModeListView; 42import com.android.camera.ui.PreviewOverlay; 43import com.android.camera.ui.PreviewStatusListener; 44import com.android.camera.util.CameraUtil; 45import com.android.camera2.R; 46 47import java.util.List; 48 49public class PhotoUI implements 50 FocusUI, PreviewStatusListener, 51 CameraManager.CameraFaceDetectionCallback { 52 53 private static final String TAG = "PhotoUI"; 54 private static final int DOWN_SAMPLE_FACTOR = 4; 55 private static final float UNSET = 0f; 56 57 private final PreviewOverlay mPreviewOverlay; 58 private CameraActivity mActivity; 59 private PhotoController mController; 60 61 private View mRootView; 62 private SurfaceTexture mSurfaceTexture; 63 64 private FaceView mFaceView; 65 private DecodeImageForReview mDecodeTaskForReview = null; 66 private Toast mNotSelectableToast; 67 68 private int mZoomMax; 69 private List<Integer> mZoomRatios; 70 71 private int mPreviewWidth = 0; 72 private int mPreviewHeight = 0; 73 private float mSurfaceTextureUncroppedWidth; 74 private float mSurfaceTextureUncroppedHeight; 75 76 private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener; 77 private TextureView mTextureView; 78 private Matrix mMatrix = null; 79 private float mAspectRatio = UNSET; 80 private final Object mSurfaceTextureLock = new Object(); 81 82 private ButtonManager.ButtonCallback mCameraCallback; 83 private ButtonManager.ButtonCallback mHdrCallback; 84 private ButtonManager.ButtonCallback mRefocusCallback; 85 86 private final OnClickListener mCancelCallback = new OnClickListener() { 87 @Override 88 public void onClick(View v) { 89 mController.onCaptureCancelled(); 90 } 91 }; 92 private final OnClickListener mDoneCallback = new OnClickListener() { 93 @Override 94 public void onClick(View v) { 95 mController.onCaptureDone(); 96 } 97 }; 98 private final OnClickListener mRetakeCallback = new OnClickListener() { 99 @Override 100 public void onClick(View v) { 101 setupIntentToggleButtons(); 102 mActivity.getCameraAppUI().transitionToIntentLayout(); 103 mController.onCaptureRetake(); 104 } 105 }; 106 107 private final GestureDetector.OnGestureListener mPreviewGestureListener 108 = new GestureDetector.SimpleOnGestureListener() { 109 @Override 110 public boolean onSingleTapUp(MotionEvent ev) { 111 mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY()); 112 return true; 113 } 114 }; 115 116 @Override 117 public GestureDetector.OnGestureListener getGestureListener() { 118 return mPreviewGestureListener; 119 } 120 121 public interface SurfaceTextureSizeChangedListener { 122 public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight); 123 } 124 125 @Override 126 public void onPreviewLayoutChanged(View v, int left, int top, int right, 127 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 128 int width = right - left; 129 int height = bottom - top; 130 if (mPreviewWidth != width || mPreviewHeight != height) { 131 mPreviewWidth = width; 132 mPreviewHeight = height; 133 } 134 } 135 136 @Override 137 public boolean shouldAutoAdjustTransformMatrixOnLayout() { 138 return true; 139 } 140 141 @Override 142 public boolean shouldAutoAdjustBottomBar() { 143 return true; 144 } 145 146 private class DecodeTask extends AsyncTask<Void, Void, Bitmap> { 147 private final byte [] mData; 148 private int mOrientation; 149 private boolean mMirror; 150 151 public DecodeTask(byte[] data, int orientation, boolean mirror) { 152 mData = data; 153 mOrientation = orientation; 154 mMirror = mirror; 155 } 156 157 @Override 158 protected Bitmap doInBackground(Void... params) { 159 // Decode image in background. 160 Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR); 161 if (mOrientation != 0 || mMirror) { 162 Matrix m = new Matrix(); 163 if (mMirror) { 164 // Flip horizontally 165 m.setScale(-1f, 1f); 166 } 167 m.preRotate(mOrientation); 168 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, 169 false); 170 } 171 return bitmap; 172 } 173 } 174 175 private class DecodeImageForReview extends DecodeTask { 176 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) { 177 super(data, orientation, mirror); 178 } 179 180 @Override 181 protected void onPostExecute(Bitmap bitmap) { 182 if (isCancelled()) { 183 return; 184 } 185 mDecodeTaskForReview = null; 186 } 187 } 188 189 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { 190 mActivity = activity; 191 mController = controller; 192 mRootView = parent; 193 194 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); 195 mActivity.getLayoutInflater().inflate(R.layout.photo_module, 196 (ViewGroup) moduleRoot, true); 197 // display the view 198 mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); 199 initIndicators(); 200 201 mSurfaceTexture = mTextureView.getSurfaceTexture(); 202 203 // Customize the bottom bar. 204 if (mActivity.getCurrentModuleIndex() == ModeListView.MODE_PHOTO) { 205 // Simple photo mode. 206 activity.getCameraAppUI().setBottomBarColor( 207 activity.getResources().getColor(R.color.camera_mode_color)); 208 } else { 209 // Advanced photo mode. 210 activity.getCameraAppUI().setBottomBarColor( 211 activity.getResources().getColor(R.color.craft_mode_color)); 212 } 213 214 ViewStub faceViewStub = (ViewStub) mRootView 215 .findViewById(R.id.face_view_stub); 216 if (faceViewStub != null) { 217 faceViewStub.inflate(); 218 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view); 219 setSurfaceTextureSizeChangedListener(mFaceView); 220 } 221 222 mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay); 223 } 224 225 public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) { 226 mSurfaceTextureSizeListener = listener; 227 } 228 229 public void updatePreviewAspectRatio(float aspectRatio) { 230 if (aspectRatio <= 0) { 231 Log.e(TAG, "Invalid aspect ratio: " + aspectRatio); 232 return; 233 } 234 if (aspectRatio < 1f) { 235 aspectRatio = 1f / aspectRatio; 236 } 237 238 if (mAspectRatio != aspectRatio) { 239 mAspectRatio = aspectRatio; 240 // Update transform matrix with the new aspect ratio. 241 mController.updatePreviewAspectRatio(mAspectRatio); 242 } 243 } 244 245 protected Object getSurfaceTextureLock() { 246 return mSurfaceTextureLock; 247 } 248 249 @Override 250 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 251 synchronized (mSurfaceTextureLock) { 252 Log.v(TAG, "SurfaceTexture ready."); 253 mSurfaceTexture = surface; 254 mController.onPreviewUIReady(); 255 } 256 } 257 258 @Override 259 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 260 // Ignored, Camera does all the work for us 261 } 262 263 @Override 264 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 265 synchronized (mSurfaceTextureLock) { 266 mSurfaceTexture = null; 267 mController.onPreviewUIDestroyed(); 268 Log.w(TAG, "SurfaceTexture destroyed"); 269 return true; 270 } 271 } 272 273 @Override 274 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 275 } 276 277 public View getRootView() { 278 return mRootView; 279 } 280 281 private void initIndicators() { 282 // TODO init toggle buttons on bottom bar here 283 } 284 285 public void onCameraOpened(Camera.Parameters params, 286 ButtonManager.ButtonCallback cameraCallback, 287 ButtonManager.ButtonCallback hdrCallback, 288 ButtonManager.ButtonCallback refocusCallback) { 289 mCameraCallback = cameraCallback; 290 mHdrCallback = hdrCallback; 291 mRefocusCallback = refocusCallback; 292 293 if (mController.isImageCaptureIntent()) { 294 setupIntentToggleButtons(); 295 mActivity.getCameraAppUI().transitionToIntentLayout(); 296 } else { 297 setupToggleButtons(); 298 } 299 300 initializeZoom(params); 301 } 302 303 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) { 304 // Decode jpeg byte array and then animate the jpeg 305 DecodeTask task = new DecodeTask(jpegData, orientation, mirror); 306 task.execute(); 307 } 308 309 private void setupToggleButtons() { 310 ButtonManager buttonManager = mActivity.getButtonManager(); 311 buttonManager.enableButton(ButtonManager.BUTTON_CAMERA, R.id.camera_toggle_button, 312 mCameraCallback, R.array.camera_id_icons); 313 buttonManager.enableButton(ButtonManager.BUTTON_FLASH, R.id.flash_toggle_button, 314 null, R.array.camera_flashmode_icons); 315 316 if (mActivity.getCurrentModuleIndex() == ModeListView.MODE_PHOTO) { 317 // Simple photo mode. 318 buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS, R.id.hdr_plus_toggle_button); 319 buttonManager.hideButton(ButtonManager.BUTTON_REFOCUS, R.id.refocus_toggle_button); 320 } else { 321 // Advanced photo mode. 322 buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS, R.id.hdr_plus_toggle_button, 323 mHdrCallback, R.array.pref_camera_hdr_plus_icons); 324 buttonManager.enableButton(ButtonManager.BUTTON_REFOCUS, R.id.refocus_toggle_button, 325 mRefocusCallback, R.array.refocus_icons); 326 } 327 } 328 329 private void setupIntentToggleButtons() { 330 setupToggleButtons(); 331 ButtonManager buttonManager = mActivity.getButtonManager(); 332 buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL, R.id.cancel_button, 333 mCancelCallback); 334 buttonManager.enablePushButton(ButtonManager.BUTTON_DONE, R.id.done_button, 335 mDoneCallback); 336 buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE, R.id.retake_button, 337 mRetakeCallback); 338 } 339 340 public void initializeControlByIntent() { 341 if (mController.isImageCaptureIntent()) { 342 setupIntentToggleButtons(); 343 mActivity.getCameraAppUI().transitionToIntentLayout(); 344 } 345 } 346 347 // called from onResume but only the first time 348 public void initializeFirstTime() { 349 350 } 351 352 // called from onResume every other time 353 public void initializeSecondTime(Camera.Parameters params) { 354 initializeZoom(params); 355 if (mController.isImageCaptureIntent()) { 356 hidePostCaptureAlert(); 357 } 358 // Removes pie menu. 359 } 360 361 public void showLocationDialog() { 362 AlertDialog alert = mActivity.getFirstTimeLocationAlert(); 363 alert.show(); 364 } 365 366 public void initializeZoom(Camera.Parameters params) { 367 if ((params == null) || !params.isZoomSupported()) return; 368 mZoomMax = params.getMaxZoom(); 369 mZoomRatios = params.getZoomRatios(); 370 // Currently we use immediate zoom for fast zooming to get better UX and 371 // there is no plan to take advantage of the smooth zoom. 372 // TODO: Need to setup a path to AppUI to do this 373 mPreviewOverlay.setupZoom(mZoomMax, params.getZoom(), mZoomRatios, new ZoomChangeListener()); 374 } 375 376 public void animateFlash() { 377 mController.startPreCaptureAnimation(); 378 } 379 380 public boolean onBackPressed() { 381 // In image capture mode, back button should: 382 // 1) if there is any popup, dismiss them, 2) otherwise, get out of 383 // image capture 384 if (mController.isImageCaptureIntent()) { 385 mController.onCaptureCancelled(); 386 return true; 387 } else if (!mController.isCameraIdle()) { 388 // ignore backs while we're taking a picture 389 return true; 390 } else { 391 return false; 392 } 393 } 394 395 protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) { 396 mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror); 397 mDecodeTaskForReview.execute(); 398 399 setupIntentToggleButtons(); 400 mActivity.getCameraAppUI().transitionToIntentReviewLayout(); 401 402 pauseFaceDetection(); 403 } 404 405 protected void hidePostCaptureAlert() { 406 if (mDecodeTaskForReview != null) { 407 mDecodeTaskForReview.cancel(true); 408 } 409 resumeFaceDetection(); 410 } 411 412 public void setDisplayOrientation(int orientation) { 413 if (mFaceView != null) { 414 mFaceView.setDisplayOrientation(orientation); 415 } 416 } 417 418 // shutter button handling 419 420 public boolean isShutterPressed() { 421 return false; 422 } 423 424 /** 425 * Enables or disables the shutter button. 426 */ 427 public void enableShutter(boolean enabled) { 428 429 } 430 431 public void pressShutterButton() { 432 433 } 434 435 private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener { 436 @Override 437 public void onZoomValueChanged(int index) { 438 mController.onZoomChanged(index); 439 } 440 441 @Override 442 public void onZoomStart() { 443 } 444 445 @Override 446 public void onZoomEnd() { 447 } 448 } 449 450 public void setSwipingEnabled(boolean enable) { 451 mActivity.setSwipingEnabled(enable); 452 } 453 454 public SurfaceTexture getSurfaceTexture() { 455 return mSurfaceTexture; 456 } 457 458 public void onPause() { 459 if (mFaceView != null) mFaceView.clear(); 460 } 461 462 // focus UI implementation 463 private FocusIndicator getFocusIndicator() { 464 return null; 465 //return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : null; 466 } 467 468 @Override 469 public boolean hasFaces() { 470 return (mFaceView != null && mFaceView.faceExists()); 471 } 472 473 public void clearFaces() { 474 if (mFaceView != null) { 475 mFaceView.clear(); 476 } 477 } 478 479 @Override 480 public void clearFocus() { 481 FocusIndicator indicator = getFocusIndicator(); 482 if (indicator != null) { 483 indicator.clear(); 484 } 485 } 486 487 @Override 488 public void setFocusPosition(int x, int y) { 489 } 490 491 @Override 492 public void onFocusStarted() { 493 FocusIndicator indicator = getFocusIndicator(); 494 if (indicator != null) { 495 indicator.showStart(); 496 } 497 } 498 499 @Override 500 public void onFocusSucceeded(boolean timeout) { 501 FocusIndicator indicator = getFocusIndicator(); 502 if (indicator != null) { 503 indicator.showSuccess(timeout); 504 } 505 } 506 507 @Override 508 public void onFocusFailed(boolean timeout) { 509 FocusIndicator indicator = getFocusIndicator(); 510 if (indicator != null) { 511 indicator.showFail(timeout); 512 } 513 } 514 515 @Override 516 public void pauseFaceDetection() { 517 if (mFaceView != null) mFaceView.pause(); 518 } 519 520 @Override 521 public void resumeFaceDetection() { 522 if (mFaceView != null) mFaceView.resume(); 523 } 524 525 public void onStartFaceDetection(int orientation, boolean mirror) { 526 mFaceView.clear(); 527 mFaceView.setVisibility(View.VISIBLE); 528 mFaceView.setDisplayOrientation(orientation); 529 mFaceView.setMirror(mirror); 530 mFaceView.resume(); 531 } 532 533 @Override 534 public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) { 535 mFaceView.setFaces(faces); 536 } 537 538} 539