PhotoUI.java revision 08b0cddea8e7390bd21053d3049ea165c759d4db
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.Matrix; 23import android.graphics.SurfaceTexture; 24import android.hardware.Camera; 25import android.hardware.Camera.Face; 26import android.os.AsyncTask; 27import android.view.GestureDetector; 28import android.view.MotionEvent; 29import android.view.View; 30import android.view.ViewGroup; 31 32import com.android.camera.FocusOverlayManager.FocusUI; 33import com.android.camera.cameradevice.CameraManager; 34import com.android.camera.debug.Log; 35import com.android.camera.ui.FaceView; 36import com.android.camera.ui.PreviewOverlay; 37import com.android.camera.ui.PreviewStatusListener; 38import com.android.camera.util.CameraUtil; 39import com.android.camera.widget.AspectRatioDialogLayout; 40import com.android.camera.widget.AspectRatioSelector; 41import com.android.camera.widget.LocationDialogLayout; 42import com.android.camera2.R; 43 44import java.util.List; 45 46public class PhotoUI implements PreviewStatusListener, 47 CameraManager.CameraFaceDetectionCallback { 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 FocusUI mFocusUI; 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 FaceView mFaceView = null; 63 private DecodeImageForReview mDecodeTaskForReview = null; 64 65 private int mZoomMax; 66 private List<Integer> mZoomRatios; 67 68 private int mPreviewWidth = 0; 69 private int mPreviewHeight = 0; 70 private float mAspectRatio = UNSET; 71 72 private final GestureDetector.OnGestureListener mPreviewGestureListener 73 = new GestureDetector.SimpleOnGestureListener() { 74 @Override 75 public boolean onSingleTapUp(MotionEvent ev) { 76 mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY()); 77 return true; 78 } 79 }; 80 private final DialogInterface.OnDismissListener mOnDismissListener 81 = new DialogInterface.OnDismissListener() { 82 @Override 83 public void onDismiss(DialogInterface dialog) { 84 mDialog = null; 85 } 86 }; 87 private Runnable mRunnableForNextFrame = null; 88 89 @Override 90 public GestureDetector.OnGestureListener getGestureListener() { 91 return mPreviewGestureListener; 92 } 93 94 @Override 95 public View.OnTouchListener getTouchListener() { 96 return null; 97 } 98 99 @Override 100 public void onPreviewLayoutChanged(View v, int left, int top, int right, 101 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 102 int width = right - left; 103 int height = bottom - top; 104 if (mPreviewWidth != width || mPreviewHeight != height) { 105 mPreviewWidth = width; 106 mPreviewHeight = height; 107 } 108 } 109 110 @Override 111 public boolean shouldAutoAdjustTransformMatrixOnLayout() { 112 return true; 113 } 114 115 @Override 116 public boolean shouldAutoAdjustBottomBar() { 117 return true; 118 } 119 120 @Override 121 public void onPreviewFlipped() { 122 mController.updateCameraOrientation(); 123 } 124 125 /** 126 * Sets the runnable to run when the next frame comes in. 127 */ 128 public void setRunnableForNextFrame(Runnable runnable) { 129 mRunnableForNextFrame = runnable; 130 } 131 132 private class DecodeTask extends AsyncTask<Void, Void, Bitmap> { 133 private final byte [] mData; 134 private final int mOrientation; 135 private final boolean mMirror; 136 137 public DecodeTask(byte[] data, int orientation, boolean mirror) { 138 mData = data; 139 mOrientation = orientation; 140 mMirror = mirror; 141 } 142 143 @Override 144 protected Bitmap doInBackground(Void... params) { 145 // Decode image in background. 146 Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR); 147 if (mOrientation != 0 || mMirror) { 148 Matrix m = new Matrix(); 149 if (mMirror) { 150 // Flip horizontally 151 m.setScale(-1f, 1f); 152 } 153 m.preRotate(mOrientation); 154 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, 155 false); 156 } 157 return bitmap; 158 } 159 } 160 161 private class DecodeImageForReview extends DecodeTask { 162 public DecodeImageForReview(byte[] data, int orientation, boolean mirror) { 163 super(data, orientation, mirror); 164 } 165 166 @Override 167 protected void onPostExecute(Bitmap bitmap) { 168 if (isCancelled()) { 169 return; 170 } 171 mDecodeTaskForReview = null; 172 } 173 } 174 175 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { 176 mActivity = activity; 177 mController = controller; 178 mRootView = parent; 179 180 ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout); 181 mActivity.getLayoutInflater().inflate(R.layout.photo_module, 182 moduleRoot, true); 183 initIndicators(); 184 mFocusUI = (FocusUI) mRootView.findViewById(R.id.focus_overlay); 185 mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay); 186 } 187 188 public FocusUI getFocusUI() { 189 return mFocusUI; 190 } 191 192 public void updatePreviewAspectRatio(float aspectRatio) { 193 if (aspectRatio <= 0) { 194 Log.e(TAG, "Invalid aspect ratio: " + aspectRatio); 195 return; 196 } 197 if (aspectRatio < 1f) { 198 aspectRatio = 1f / aspectRatio; 199 } 200 201 if (mAspectRatio != aspectRatio) { 202 mAspectRatio = aspectRatio; 203 // Update transform matrix with the new aspect ratio. 204 mController.updatePreviewAspectRatio(mAspectRatio); 205 } 206 } 207 208 @Override 209 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 210 mController.onPreviewUIReady(); 211 } 212 213 @Override 214 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 215 // Ignored, Camera does all the work for us 216 } 217 218 @Override 219 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 220 mController.onPreviewUIDestroyed(); 221 return true; 222 } 223 224 @Override 225 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 226 if (mRunnableForNextFrame != null) { 227 mRootView.post(mRunnableForNextFrame); 228 mRunnableForNextFrame = null; 229 } 230 } 231 232 public View getRootView() { 233 return mRootView; 234 } 235 236 private void initIndicators() { 237 // TODO init toggle buttons on bottom bar here 238 } 239 240 public void onCameraOpened(Camera.Parameters params) { 241 initializeZoom(params); 242 } 243 244 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) { 245 // Decode jpeg byte array and then animate the jpeg 246 DecodeTask task = new DecodeTask(jpegData, orientation, mirror); 247 task.execute(); 248 } 249 250 // called from onResume but only the first time 251 public void initializeFirstTime() { 252 253 } 254 255 // called from onResume every other time 256 public void initializeSecondTime(Camera.Parameters params) { 257 initializeZoom(params); 258 if (mController.isImageCaptureIntent()) { 259 hidePostCaptureAlert(); 260 } 261 // Removes pie menu. 262 } 263 264 public void showLocationAndAspectRatioDialog( 265 final PhotoModule.LocationDialogCallback locationCallback, 266 final PhotoModule.AspectRatioDialogCallback aspectRatioDialogCallback) { 267 setDialog(new Dialog(mActivity, 268 android.R.style.Theme_Black_NoTitleBar_Fullscreen)); 269 final LocationDialogLayout locationDialogLayout = (LocationDialogLayout) mActivity 270 .getLayoutInflater().inflate(R.layout.location_dialog_layout, null); 271 locationDialogLayout.setLocationTaggingSelectionListener( 272 new LocationDialogLayout.LocationTaggingSelectionListener() { 273 @Override 274 public void onLocationTaggingSelected(boolean selected) { 275 // Update setting. 276 locationCallback.onLocationTaggingSelected(selected); 277 // Go to next page. 278 showAspectRatioDialog(aspectRatioDialogCallback, mDialog); 279 } 280 }); 281 mDialog.setContentView(locationDialogLayout, new ViewGroup.LayoutParams( 282 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 283 mDialog.show(); 284 } 285 286 /** 287 * Dismisses previous dialog if any, sets current dialog to the given dialog, 288 * and set the on dismiss listener for the given dialog. 289 * @param dialog dialog to show 290 */ 291 private void setDialog(Dialog dialog) { 292 if (mDialog != null) { 293 mDialog.setOnDismissListener(null); 294 mDialog.dismiss(); 295 } 296 mDialog = dialog; 297 if (mDialog != null) { 298 mDialog.setOnDismissListener(mOnDismissListener); 299 } 300 } 301 302 public void showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback) { 303 setDialog(new Dialog(mActivity, android.R.style.Theme_Black_NoTitleBar_Fullscreen)); 304 showAspectRatioDialog(callback, mDialog); 305 } 306 307 private void showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback, 308 final Dialog aspectRatioDialog) { 309 if (aspectRatioDialog == null) { 310 Log.e(TAG, "Dialog for aspect ratio is null."); 311 return; 312 } 313 final AspectRatioDialogLayout aspectRatioDialogLayout = 314 (AspectRatioDialogLayout) mActivity 315 .getLayoutInflater().inflate(R.layout.aspect_ratio_dialog_layout, null); 316 aspectRatioDialogLayout.initialize( 317 new AspectRatioDialogLayout.AspectRatioChangedListener() { 318 @Override 319 public void onAspectRatioChanged(AspectRatioSelector.AspectRatio aspectRatio) { 320 // callback to set picture size. 321 callback.onAspectRatioSelected(aspectRatio, new Runnable() { 322 @Override 323 public void run() { 324 if (mDialog != null) { 325 mDialog.dismiss(); 326 } 327 } 328 }); 329 } 330 }, callback.get4x3AspectRatioText(), callback.get16x9AspectRatioText(), 331 callback.getCurrentAspectRatio()); 332 aspectRatioDialog.setContentView(aspectRatioDialogLayout, new ViewGroup.LayoutParams( 333 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 334 aspectRatioDialog.show(); 335 } 336 337 public void initializeZoom(Camera.Parameters params) { 338 if ((params == null) || !params.isZoomSupported()) return; 339 mZoomMax = params.getMaxZoom(); 340 mZoomRatios = params.getZoomRatios(); 341 // Currently we use immediate zoom for fast zooming to get better UX and 342 // there is no plan to take advantage of the smooth zoom. 343 // TODO: Need to setup a path to AppUI to do this 344 mPreviewOverlay.setupZoom(mZoomMax, params.getZoom(), mZoomRatios, new ZoomChangeListener()); 345 } 346 347 public void animateFlash() { 348 mController.startPreCaptureAnimation(); 349 } 350 351 public boolean onBackPressed() { 352 // In image capture mode, back button should: 353 // 1) if there is any popup, dismiss them, 2) otherwise, get out of 354 // image capture 355 if (mController.isImageCaptureIntent()) { 356 mController.onCaptureCancelled(); 357 return true; 358 } else if (!mController.isCameraIdle()) { 359 // ignore backs while we're taking a picture 360 return true; 361 } else { 362 return false; 363 } 364 } 365 366 protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) { 367 mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror); 368 mDecodeTaskForReview.execute(); 369 370 mActivity.getCameraAppUI().transitionToIntentReviewLayout(); 371 pauseFaceDetection(); 372 } 373 374 protected void hidePostCaptureAlert() { 375 if (mDecodeTaskForReview != null) { 376 mDecodeTaskForReview.cancel(true); 377 } 378 resumeFaceDetection(); 379 } 380 381 public void setDisplayOrientation(int orientation) { 382 if (mFaceView != null) { 383 mFaceView.setDisplayOrientation(orientation); 384 } 385 } 386 387 private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener { 388 @Override 389 public void onZoomValueChanged(int index) { 390 mController.onZoomChanged(index); 391 } 392 393 @Override 394 public void onZoomStart() { 395 } 396 397 @Override 398 public void onZoomEnd() { 399 } 400 } 401 402 public void setSwipingEnabled(boolean enable) { 403 mActivity.setSwipingEnabled(enable); 404 } 405 406 public void onPause() { 407 if (mFaceView != null) mFaceView.clear(); 408 if (mDialog != null) { 409 mDialog.dismiss(); 410 } 411 } 412 413 public void clearFaces() { 414 if (mFaceView != null) { 415 mFaceView.clear(); 416 } 417 } 418 419 public void pauseFaceDetection() { 420 if (mFaceView != null) mFaceView.pause(); 421 } 422 423 public void resumeFaceDetection() { 424 if (mFaceView != null) mFaceView.resume(); 425 } 426 427 public void onStartFaceDetection(int orientation, boolean mirror) { 428 if (mFaceView != null) { 429 mFaceView.clear(); 430 mFaceView.setVisibility(View.VISIBLE); 431 mFaceView.setDisplayOrientation(orientation); 432 mFaceView.setMirror(mirror); 433 mFaceView.resume(); 434 } 435 } 436 437 @Override 438 public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) { 439 if (mFaceView != null) { 440 mFaceView.setFaces(faces); 441 } 442 } 443 444 /** 445 * Returns a {@link com.android.camera.ui.PreviewStatusListener.PreviewAreaChangedListener} 446 * that should be registered to listen to preview area change. 447 */ 448 public PreviewAreaChangedListener getPreviewAreaSizeChangedListener() { 449 return mFaceView; 450 } 451 452} 453