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.RectF;
23 import android.graphics.SurfaceTexture;
24 import android.hardware.Camera.Face;
25 import android.os.AsyncTask;
26 import android.view.GestureDetector;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.FrameLayout;
31 import android.widget.ImageView;
32 
33 import com.android.camera.captureintent.PictureDecoder;
34 import com.android.camera.debug.DebugPropertyHelper;
35 import com.android.camera.debug.Log;
36 import com.android.camera.ui.CountDownView;
37 import com.android.camera.ui.FaceView;
38 import com.android.camera.ui.PreviewOverlay;
39 import com.android.camera.ui.PreviewStatusListener;
40 import com.android.camera.ui.focus.FocusRing;
41 import com.android.camera2.R;
42 import com.android.ex.camera2.portability.CameraAgent;
43 import com.android.ex.camera2.portability.CameraCapabilities;
44 import com.android.ex.camera2.portability.CameraSettings;
45 
46 public class PhotoUI implements PreviewStatusListener,
47     CameraAgent.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener {
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 FocusRing mFocusRing;
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 final FaceView mFaceView;
63     private DecodeImageForReview mDecodeTaskForReview = null;
64 
65     private float mZoomMax;
66 
67     private int mPreviewWidth = 0;
68     private int mPreviewHeight = 0;
69     private float mAspectRatio = UNSET;
70 
71     private ImageView mIntentReviewImageView;
72 
73     private final GestureDetector.OnGestureListener mPreviewGestureListener
74             = new GestureDetector.SimpleOnGestureListener() {
75         @Override
76         public boolean onSingleTapUp(MotionEvent ev) {
77             mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY());
78             return true;
79         }
80     };
81     private final DialogInterface.OnDismissListener mOnDismissListener
82             = new DialogInterface.OnDismissListener() {
83         @Override
84         public void onDismiss(DialogInterface dialog) {
85             mDialog = null;
86         }
87     };
88     private final CountDownView mCountdownView;
89 
90     @Override
getGestureListener()91     public GestureDetector.OnGestureListener getGestureListener() {
92         return mPreviewGestureListener;
93     }
94 
95     @Override
getTouchListener()96     public View.OnTouchListener getTouchListener() {
97         return null;
98     }
99 
100     @Override
onPreviewLayoutChanged(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)101     public void onPreviewLayoutChanged(View v, int left, int top, int right,
102             int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
103         int width = right - left;
104         int height = bottom - top;
105         if (mPreviewWidth != width || mPreviewHeight != height) {
106             mPreviewWidth = width;
107             mPreviewHeight = height;
108         }
109     }
110 
111     @Override
shouldAutoAdjustTransformMatrixOnLayout()112     public boolean shouldAutoAdjustTransformMatrixOnLayout() {
113         return true;
114     }
115 
116     @Override
onPreviewFlipped()117     public void onPreviewFlipped() {
118         mController.updateCameraOrientation();
119     }
120 
121     /**
122      * Starts the countdown timer.
123      *
124      * @param sec seconds to countdown
125      */
startCountdown(int sec)126     public void startCountdown(int sec) {
127         mCountdownView.startCountDown(sec);
128     }
129 
130     /**
131      * Sets a listener that gets notified when the countdown is finished.
132      */
setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener)133     public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
134         mCountdownView.setCountDownStatusListener(listener);
135     }
136 
137     /**
138      * Returns whether the countdown is on-going.
139      */
isCountingDown()140     public boolean isCountingDown() {
141         return mCountdownView.isCountingDown();
142     }
143 
144     /**
145      * Cancels the on-going countdown, if any.
146      */
cancelCountDown()147     public void cancelCountDown() {
148         mCountdownView.cancelCountDown();
149     }
150 
151     @Override
onPreviewAreaChanged(RectF previewArea)152     public void onPreviewAreaChanged(RectF previewArea) {
153         if (mFaceView != null) {
154             mFaceView.onPreviewAreaChanged(previewArea);
155         }
156         mCountdownView.onPreviewAreaChanged(previewArea);
157     }
158 
159     private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
160         private final byte [] mData;
161         private final int mOrientation;
162         private final boolean mMirror;
163 
DecodeTask(byte[] data, int orientation, boolean mirror)164         public DecodeTask(byte[] data, int orientation, boolean mirror) {
165             mData = data;
166             mOrientation = orientation;
167             mMirror = mirror;
168         }
169 
170         @Override
doInBackground(Void... params)171         protected Bitmap doInBackground(Void... params) {
172             // Decode image in background.
173             return PictureDecoder.decode(mData, DOWN_SAMPLE_FACTOR, mOrientation, mMirror);
174         }
175     }
176 
177     private class DecodeImageForReview extends DecodeTask {
DecodeImageForReview(byte[] data, int orientation, boolean mirror)178         public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
179             super(data, orientation, mirror);
180         }
181 
182         @Override
onPostExecute(Bitmap bitmap)183         protected void onPostExecute(Bitmap bitmap) {
184             if (isCancelled()) {
185                 return;
186             }
187 
188             mIntentReviewImageView.setImageBitmap(bitmap);
189             showIntentReviewImageView();
190 
191             mDecodeTaskForReview = null;
192         }
193     }
194 
PhotoUI(CameraActivity activity, PhotoController controller, View parent)195     public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
196         mActivity = activity;
197         mController = controller;
198         mRootView = parent;
199 
200         ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
201         mActivity.getLayoutInflater().inflate(R.layout.photo_module,
202                  moduleRoot, true);
203         initIndicators();
204         mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring);
205         mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
206         mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
207         // Show faces if we are in debug mode.
208         if (DebugPropertyHelper.showCaptureDebugUI()) {
209             mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
210         } else {
211             mFaceView = null;
212         }
213 
214         if (mController.isImageCaptureIntent()) {
215             initIntentReviewImageView();
216         }
217     }
218 
initIntentReviewImageView()219     private void initIntentReviewImageView() {
220         mIntentReviewImageView = (ImageView) mRootView.findViewById(R.id.intent_review_imageview);
221         mActivity.getCameraAppUI().addPreviewAreaChangedListener(
222                 new PreviewStatusListener.PreviewAreaChangedListener() {
223                     @Override
224                     public void onPreviewAreaChanged(RectF previewArea) {
225                         FrameLayout.LayoutParams params =
226                             (FrameLayout.LayoutParams) mIntentReviewImageView.getLayoutParams();
227                         params.width = (int) previewArea.width();
228                         params.height = (int) previewArea.height();
229                         params.setMargins((int) previewArea.left, (int) previewArea.top, 0, 0);
230                         mIntentReviewImageView.setLayoutParams(params);
231                     }
232                 });
233     }
234 
235     /**
236      * Show the image review over the live preview for intent captures.
237      */
showIntentReviewImageView()238     public void showIntentReviewImageView() {
239         if (mIntentReviewImageView != null) {
240             mIntentReviewImageView.setVisibility(View.VISIBLE);
241         }
242     }
243 
244     /**
245      * Hide the image review over the live preview for intent captures.
246      */
hideIntentReviewImageView()247     public void hideIntentReviewImageView() {
248         if (mIntentReviewImageView != null) {
249             mIntentReviewImageView.setVisibility(View.INVISIBLE);
250         }
251     }
252 
253 
getFocusRing()254     public FocusRing getFocusRing() {
255         return mFocusRing;
256     }
257 
updatePreviewAspectRatio(float aspectRatio)258     public void updatePreviewAspectRatio(float aspectRatio) {
259         if (aspectRatio <= 0) {
260             Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
261             return;
262         }
263         if (aspectRatio < 1f) {
264             aspectRatio = 1f / aspectRatio;
265         }
266 
267         if (mAspectRatio != aspectRatio) {
268             mAspectRatio = aspectRatio;
269             // Update transform matrix with the new aspect ratio.
270             mController.updatePreviewAspectRatio(mAspectRatio);
271         }
272     }
273 
274     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)275     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
276         mController.onPreviewUIReady();
277     }
278 
279     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)280     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
281         // Ignored, Camera does all the work for us
282     }
283 
284     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)285     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
286         mController.onPreviewUIDestroyed();
287         return true;
288     }
289 
290     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)291     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
292     }
293 
initIndicators()294     private void initIndicators() {
295         // TODO init toggle buttons on bottom bar here
296     }
297 
onCameraOpened(CameraCapabilities capabilities, CameraSettings settings)298     public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
299         initializeZoom(capabilities, settings);
300     }
301 
animateCapture(final byte[] jpegData, int orientation, boolean mirror)302     public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
303         // Decode jpeg byte array and then animate the jpeg
304         DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
305         task.execute();
306     }
307 
308     // called from onResume but only the first time
initializeFirstTime()309     public void initializeFirstTime() {
310 
311     }
312 
313     // called from onResume every other time
initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings)314     public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) {
315         initializeZoom(capabilities, settings);
316         if (mController.isImageCaptureIntent()) {
317             hidePostCaptureAlert();
318         }
319     }
320 
initializeZoom(CameraCapabilities capabilities, CameraSettings settings)321     public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
322         if ((capabilities == null) || settings == null ||
323                 !capabilities.supports(CameraCapabilities.Feature.ZOOM)) {
324             return;
325         }
326         mZoomMax = capabilities.getMaxZoomRatio();
327         // Currently we use immediate zoom for fast zooming to get better UX and
328         // there is no plan to take advantage of the smooth zoom.
329         // TODO: Need to setup a path to AppUI to do this
330         mPreviewOverlay.setupZoom(mZoomMax, settings.getCurrentZoomRatio(),
331                 new ZoomChangeListener());
332     }
333 
animateFlash()334     public void animateFlash() {
335         mController.startPreCaptureAnimation();
336     }
337 
onBackPressed()338     public boolean onBackPressed() {
339         // In image capture mode, back button should:
340         // 1) if there is any popup, dismiss them, 2) otherwise, get out of
341         // image capture
342         if (mController.isImageCaptureIntent()) {
343             mController.onCaptureCancelled();
344             return true;
345         } else if (!mController.isCameraIdle()) {
346             // ignore backs while we're taking a picture
347             return true;
348         } else {
349             return false;
350         }
351     }
352 
showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror)353     protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
354         mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
355         mDecodeTaskForReview.execute();
356 
357         mActivity.getCameraAppUI().transitionToIntentReviewLayout();
358         pauseFaceDetection();
359     }
360 
hidePostCaptureAlert()361     protected void hidePostCaptureAlert() {
362         if (mDecodeTaskForReview != null) {
363             mDecodeTaskForReview.cancel(true);
364         }
365         resumeFaceDetection();
366     }
367 
setDisplayOrientation(int orientation)368     public void setDisplayOrientation(int orientation) {
369         if (mFaceView != null) {
370             mFaceView.setDisplayOrientation(orientation);
371         }
372     }
373 
374     private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
375         @Override
onZoomValueChanged(float ratio)376         public void onZoomValueChanged(float ratio) {
377             mController.onZoomChanged(ratio);
378         }
379 
380         @Override
onZoomStart()381         public void onZoomStart() {
382         }
383 
384         @Override
onZoomEnd()385         public void onZoomEnd() {
386         }
387     }
388 
setSwipingEnabled(boolean enable)389     public void setSwipingEnabled(boolean enable) {
390         mActivity.setSwipingEnabled(enable);
391     }
392 
onPause()393     public void onPause() {
394         if (mFaceView != null) {
395             mFaceView.clear();
396         }
397         if (mDialog != null) {
398             mDialog.dismiss();
399         }
400         // recalculate aspect ratio when restarting.
401         mAspectRatio = 0.0f;
402     }
403 
clearFaces()404     public void clearFaces() {
405         if (mFaceView != null) {
406             mFaceView.clear();
407         }
408     }
409 
pauseFaceDetection()410     public void pauseFaceDetection() {
411         if (mFaceView != null) {
412             mFaceView.pause();
413         }
414     }
415 
resumeFaceDetection()416     public void resumeFaceDetection() {
417         if (mFaceView != null) {
418             mFaceView.resume();
419         }
420     }
421 
onStartFaceDetection(int orientation, boolean mirror)422     public void onStartFaceDetection(int orientation, boolean mirror) {
423         if (mFaceView != null) {
424             mFaceView.clear();
425             mFaceView.setVisibility(View.VISIBLE);
426             mFaceView.setDisplayOrientation(orientation);
427             mFaceView.setMirror(mirror);
428             mFaceView.resume();
429         }
430     }
431 
432     @Override
onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera)433     public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
434         if (mFaceView != null) {
435             mFaceView.setFaces(faces);
436         }
437     }
438 
439 }
440