OSDN Git Service

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