• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ApiHelper;
43 import com.android.camera.util.CameraUtil;
44 import com.android.camera.util.GservicesHelper;
45 import com.android.camera.widget.AspectRatioDialogLayout;
46 import com.android.camera.widget.AspectRatioSelector;
47 import com.android.camera.widget.LocationDialogLayout;
48 import com.android.camera2.R;
49 import com.android.ex.camera2.portability.CameraAgent;
50 import com.android.ex.camera2.portability.CameraCapabilities;
51 import com.android.ex.camera2.portability.CameraSettings;
52 
53 public 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;
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 final CountDownView mCountdownView;
97 
98     @Override
getGestureListener()99     public GestureDetector.OnGestureListener getGestureListener() {
100         return mPreviewGestureListener;
101     }
102 
103     @Override
getTouchListener()104     public View.OnTouchListener getTouchListener() {
105         return null;
106     }
107 
108     @Override
onPreviewLayoutChanged(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)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
shouldAutoAdjustTransformMatrixOnLayout()120     public boolean shouldAutoAdjustTransformMatrixOnLayout() {
121         return true;
122     }
123 
124     @Override
shouldAutoAdjustBottomBar()125     public boolean shouldAutoAdjustBottomBar() {
126         return true;
127     }
128 
129     @Override
onPreviewFlipped()130     public void onPreviewFlipped() {
131         mController.updateCameraOrientation();
132     }
133 
134     /**
135      * Sets the runnable to run when the next frame comes in.
136      */
setRunnableForNextFrame(Runnable runnable)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      */
startCountdown(int sec)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      */
setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener)153     public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
154         mCountdownView.setCountDownStatusListener(listener);
155     }
156 
157     /**
158      * Returns whether the countdown is on-going.
159      */
isCountingDown()160     public boolean isCountingDown() {
161         return mCountdownView.isCountingDown();
162     }
163 
164     /**
165      * Cancels the on-going countdown, if any.
166      */
cancelCountDown()167     public void cancelCountDown() {
168         mCountdownView.cancelCountDown();
169     }
170 
171     @Override
onPreviewAreaChanged(RectF previewArea)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 
DecodeTask(byte[] data, int orientation, boolean mirror)184         public DecodeTask(byte[] data, int orientation, boolean mirror) {
185             mData = data;
186             mOrientation = orientation;
187             mMirror = mirror;
188         }
189 
190         @Override
doInBackground(Void... params)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 {
DecodeImageForReview(byte[] data, int orientation, boolean mirror)209         public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
210             super(data, orientation, mirror);
211         }
212 
213         @Override
onPostExecute(Bitmap bitmap)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 
PhotoUI(CameraActivity activity, PhotoController controller, View parent)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         // Show faces if we are in debug mode.
239         if (DebugPropertyHelper.showCaptureDebugUI()) {
240             mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
241         } else {
242             mFaceView = null;
243         }
244 
245         if (mController.isImageCaptureIntent()) {
246             initIntentReviewImageView();
247         }
248     }
249 
initIntentReviewImageView()250     private void initIntentReviewImageView() {
251         mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview);
252         mActivity.getCameraAppUI().addPreviewAreaChangedListener(
253                 new PreviewStatusListener.PreviewAreaChangedListener() {
254                     @Override
255                     public void onPreviewAreaChanged(RectF previewArea) {
256                         FrameLayout.LayoutParams params =
257                             (FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams();
258                         params.width = (int) previewArea.width();
259                         params.height = (int) previewArea.height();
260                         params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0);
261                         mIntentReviewImageView.setLayoutParams(params);
262                     }
263                 });
264     }
265 
266     /**
267      * Show the image review over the live preview for intent captures.
268      */
showIntentReviewImageView()269     public void showIntentReviewImageView() {
270         if (mIntentReviewImageView != null) {
271             mIntentReviewImageView.setVisibility(View.VISIBLE);
272         }
273     }
274 
275     /**
276      * Hide the image review over the live preview for intent captures.
277      */
hideIntentReviewImageView()278     public void hideIntentReviewImageView() {
279         if (mIntentReviewImageView != null) {
280             mIntentReviewImageView.setVisibility(View.INVISIBLE);
281         }
282     }
283 
284 
getFocusUI()285     public FocusUI getFocusUI() {
286         return mFocusUI;
287     }
288 
updatePreviewAspectRatio(float aspectRatio)289     public void updatePreviewAspectRatio(float aspectRatio) {
290         if (aspectRatio <= 0) {
291             Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
292             return;
293         }
294         if (aspectRatio < 1f) {
295             aspectRatio = 1f / aspectRatio;
296         }
297 
298         if (mAspectRatio != aspectRatio) {
299             mAspectRatio = aspectRatio;
300             // Update transform matrix with the new aspect ratio.
301             mController.updatePreviewAspectRatio(mAspectRatio);
302         }
303     }
304 
305     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)306     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
307         mController.onPreviewUIReady();
308     }
309 
310     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)311     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
312         // Ignored, Camera does all the work for us
313     }
314 
315     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)316     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
317         mController.onPreviewUIDestroyed();
318         return true;
319     }
320 
321     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)322     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
323         if (mRunnableForNextFrame != null) {
324             mRootView.post(mRunnableForNextFrame);
325             mRunnableForNextFrame = null;
326         }
327     }
328 
getRootView()329     public View getRootView() {
330         return mRootView;
331     }
332 
initIndicators()333     private void initIndicators() {
334         // TODO init toggle buttons on bottom bar here
335     }
336 
onCameraOpened(CameraCapabilities capabilities, CameraSettings settings)337     public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
338         initializeZoom(capabilities, settings);
339     }
340 
animateCapture(final byte[] jpegData, int orientation, boolean mirror)341     public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
342         // Decode jpeg byte array and then animate the jpeg
343         DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
344         task.execute();
345     }
346 
347     // called from onResume but only the first time
initializeFirstTime()348     public void initializeFirstTime() {
349 
350     }
351 
352     // called from onResume every other time
initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings)353     public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) {
354         initializeZoom(capabilities, settings);
355         if (mController.isImageCaptureIntent()) {
356             hidePostCaptureAlert();
357         }
358     }
359 
showLocationAndAspectRatioDialog( final PhotoModule.LocationDialogCallback locationCallback, final PhotoModule.AspectRatioDialogCallback aspectRatioDialogCallback)360     public void showLocationAndAspectRatioDialog(
361             final PhotoModule.LocationDialogCallback locationCallback,
362             final PhotoModule.AspectRatioDialogCallback aspectRatioDialogCallback) {
363         setDialog(new Dialog(mActivity,
364                 android.R.style.Theme_Black_NoTitleBar_Fullscreen));
365         final LocationDialogLayout locationDialogLayout = (LocationDialogLayout) mActivity
366                 .getLayoutInflater().inflate(R.layout.location_dialog_layout, null);
367         locationDialogLayout.setLocationTaggingSelectionListener(
368                 new LocationDialogLayout.LocationTaggingSelectionListener() {
369             @Override
370             public void onLocationTaggingSelected(boolean selected) {
371                 // Update setting.
372                 locationCallback.onLocationTaggingSelected(selected);
373 
374                 if (showAspectRatioDialogOnThisDevice()) {
375                     // Go to next page.
376                     showAspectRatioDialog(aspectRatioDialogCallback, mDialog);
377                 } else {
378                     // If we don't want to show the aspect ratio dialog,
379                     // dismiss the dialog right after the user chose the
380                     // location setting.
381                     if (mDialog != null) {
382                         mDialog.dismiss();
383                     }
384                 }
385             }
386         });
387         mDialog.setContentView(locationDialogLayout, new ViewGroup.LayoutParams(
388                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
389         mDialog.show();
390     }
391 
392     /**
393      * Dismisses previous dialog if any, sets current dialog to the given dialog,
394      * and set the on dismiss listener for the given dialog.
395      * @param dialog dialog to show
396      */
setDialog(Dialog dialog)397     private void setDialog(Dialog dialog) {
398         if (mDialog != null) {
399             mDialog.setOnDismissListener(null);
400             mDialog.dismiss();
401         }
402         mDialog = dialog;
403         if (mDialog != null) {
404             mDialog.setOnDismissListener(mOnDismissListener);
405         }
406     }
407 
408     /**
409      * @return Whether the dialog was shown.
410      */
showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback)411     public boolean showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback) {
412         if (showAspectRatioDialogOnThisDevice()) {
413             setDialog(new Dialog(mActivity, android.R.style.Theme_Black_NoTitleBar_Fullscreen));
414             showAspectRatioDialog(callback, mDialog);
415             return true;
416         } else {
417             return false;
418         }
419     }
420 
showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback, final Dialog aspectRatioDialog)421     private boolean showAspectRatioDialog(final PhotoModule.AspectRatioDialogCallback callback,
422             final Dialog aspectRatioDialog) {
423         if (aspectRatioDialog == null) {
424             Log.e(TAG, "Dialog for aspect ratio is null.");
425             return false;
426         }
427         final AspectRatioDialogLayout aspectRatioDialogLayout =
428                 (AspectRatioDialogLayout) mActivity
429                 .getLayoutInflater().inflate(R.layout.aspect_ratio_dialog_layout, null);
430         aspectRatioDialogLayout.initialize(
431                 new AspectRatioDialogLayout.AspectRatioChangedListener() {
432                     @Override
433                     public void onAspectRatioChanged(AspectRatioSelector.AspectRatio aspectRatio) {
434                         // callback to set picture size.
435                         callback.onAspectRatioSelected(aspectRatio, new Runnable() {
436                             @Override
437                             public void run() {
438                                 if (mDialog != null) {
439                                     mDialog.dismiss();
440                                 }
441                             }
442                         });
443                     }
444                 }, callback.getCurrentAspectRatio());
445         aspectRatioDialog.setContentView(aspectRatioDialogLayout, new ViewGroup.LayoutParams(
446                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
447         aspectRatioDialog.show();
448         return true;
449     }
450 
451     /**
452      * @return Whether this is a device that we should show the aspect ratio
453      *         intro dialog on.
454      */
showAspectRatioDialogOnThisDevice()455     private boolean showAspectRatioDialogOnThisDevice() {
456         // We only want to show that dialog on N4/N5/N6
457         // Don't show if using API2 portability, b/17462976
458         return !GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity) &&
459                 (ApiHelper.IS_NEXUS_4 || ApiHelper.IS_NEXUS_5 || ApiHelper.IS_NEXUS_6);
460     }
461 
initializeZoom(CameraCapabilities capabilities, CameraSettings settings)462     public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
463         if ((capabilities == null) || settings == null ||
464                 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
465             return;
466         }
467         mZoomMax = capabilities.getMaxZoomRatio();
468         // Currently we use immediate zoom for fast zooming to get better UX and
469         // there is no plan to take advantage of the smooth zoom.
470         // TODO: Need to setup a path to AppUI to do this
471         mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomRatio(),
472                 new ZoomChangeListener());
473     }
474 
animateFlash()475     public void animateFlash() {
476         mController.startPreCaptureAnimation();
477     }
478 
onBackPressed()479     public boolean onBackPressed() {
480         // In image capture mode, back button should:
481         // 1) if there is any popup, dismiss them, 2) otherwise, get out of
482         // image capture
483         if (mController.isImageCaptureIntent()) {
484             mController.onCaptureCancelled();
485             return true;
486         } else if (!mController.isCameraIdle()) {
487             // ignore backs while we're taking a picture
488             return true;
489         } else {
490             return false;
491         }
492     }
493 
showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror)494     protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
495         mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
496         mDecodeTaskForReview.execute();
497 
498         mActivity.getCameraAppUI().transitionToIntentReviewLayout();
499         pauseFaceDetection();
500     }
501 
hidePostCaptureAlert()502     protected void hidePostCaptureAlert() {
503         if (mDecodeTaskForReview != null) {
504             mDecodeTaskForReview.cancel(true);
505         }
506         resumeFaceDetection();
507     }
508 
setDisplayOrientation(int orientation)509     public void setDisplayOrientation(int orientation) {
510         if (mFaceView != null) {
511             mFaceView.setDisplayOrientation(orientation);
512         }
513     }
514 
515     private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
516         @Override
onZoomValueChanged(float ratio)517         public void onZoomValueChanged(float ratio) {
518             mController.onZoomChanged(ratio);
519         }
520 
521         @Override
onZoomStart()522         public void onZoomStart() {
523         }
524 
525         @Override
onZoomEnd()526         public void onZoomEnd() {
527         }
528     }
529 
setSwipingEnabled(boolean enable)530     public void setSwipingEnabled(boolean enable) {
531         mActivity.setSwipingEnabled(enable);
532     }
533 
onPause()534     public void onPause() {
535         if (mFaceView != null) {
536             mFaceView.clear();
537         }
538         if (mDialog != null) {
539             mDialog.dismiss();
540         }
541         // recalculate aspect ratio when restarting.
542         mAspectRatio = 0.0f;
543     }
544 
clearFaces()545     public void clearFaces() {
546         if (mFaceView != null) {
547             mFaceView.clear();
548         }
549     }
550 
pauseFaceDetection()551     public void pauseFaceDetection() {
552         if (mFaceView != null) {
553             mFaceView.pause();
554         }
555     }
556 
resumeFaceDetection()557     public void resumeFaceDetection() {
558         if (mFaceView != null) {
559             mFaceView.resume();
560         }
561     }
562 
onStartFaceDetection(int orientation, boolean mirror)563     public void onStartFaceDetection(int orientation, boolean mirror) {
564         if (mFaceView != null) {
565             mFaceView.clear();
566             mFaceView.setVisibility(View.VISIBLE);
567             mFaceView.setDisplayOrientation(orientation);
568             mFaceView.setMirror(mirror);
569             mFaceView.resume();
570         }
571     }
572 
573     @Override
onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera)574     public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
575         if (mFaceView != null) {
576             mFaceView.setFaces(faces);
577         }
578     }
579 
580 }
581