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.RectF;
23import android.graphics.SurfaceTexture;
24import android.hardware.Camera.Face;
25import android.os.AsyncTask;
26import android.view.GestureDetector;
27import android.view.MotionEvent;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.FrameLayout;
31import android.widget.ImageView;
32
33import com.android.camera.captureintent.PictureDecoder;
34import com.android.camera.debug.DebugPropertyHelper;
35import com.android.camera.debug.Log;
36import com.android.camera.ui.CountDownView;
37import com.android.camera.ui.FaceView;
38import com.android.camera.ui.PreviewOverlay;
39import com.android.camera.ui.PreviewStatusListener;
40import com.android.camera.ui.focus.FocusRing;
41import com.android.camera2.R;
42import com.android.ex.camera2.portability.CameraAgent;
43import com.android.ex.camera2.portability.CameraCapabilities;
44import com.android.ex.camera2.portability.CameraSettings;
45
46public 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
91    public GestureDetector.OnGestureListener getGestureListener() {
92        return mPreviewGestureListener;
93    }
94
95    @Override
96    public View.OnTouchListener getTouchListener() {
97        return null;
98    }
99
100    @Override
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
112    public boolean shouldAutoAdjustTransformMatrixOnLayout() {
113        return true;
114    }
115
116    @Override
117    public void onPreviewFlipped() {
118        mController.updateCameraOrientation();
119    }
120
121    /**
122     * Starts the countdown timer.
123     *
124     * @param sec seconds to countdown
125     */
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     */
133    public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
134        mCountdownView.setCountDownStatusListener(listener);
135    }
136
137    /**
138     * Returns whether the countdown is on-going.
139     */
140    public boolean isCountingDown() {
141        return mCountdownView.isCountingDown();
142    }
143
144    /**
145     * Cancels the on-going countdown, if any.
146     */
147    public void cancelCountDown() {
148        mCountdownView.cancelCountDown();
149    }
150
151    @Override
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
164        public DecodeTask(byte[] data, int orientation, boolean mirror) {
165            mData = data;
166            mOrientation = orientation;
167            mMirror = mirror;
168        }
169
170        @Override
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 {
178        public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
179            super(data, orientation, mirror);
180        }
181
182        @Override
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
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
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     */
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     */
247    public void hideIntentReviewImageView() {
248        if (mIntentReviewImageView != null) {
249            mIntentReviewImageView.setVisibility(View.INVISIBLE);
250        }
251    }
252
253
254    public FocusRing getFocusRing() {
255        return mFocusRing;
256    }
257
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
275    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
276        mController.onPreviewUIReady();
277    }
278
279    @Override
280    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
281        // Ignored, Camera does all the work for us
282    }
283
284    @Override
285    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
286        mController.onPreviewUIDestroyed();
287        return true;
288    }
289
290    @Override
291    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
292    }
293
294    private void initIndicators() {
295        // TODO init toggle buttons on bottom bar here
296    }
297
298    public void onCameraOpened(CameraCapabilities capabilities, CameraSettings settings) {
299        initializeZoom(capabilities, settings);
300    }
301
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
309    public void initializeFirstTime() {
310
311    }
312
313    // called from onResume every other time
314    public void initializeSecondTime(CameraCapabilities capabilities, CameraSettings settings) {
315        initializeZoom(capabilities, settings);
316        if (mController.isImageCaptureIntent()) {
317            hidePostCaptureAlert();
318        }
319    }
320
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
334    public void animateFlash() {
335        mController.startPreCaptureAnimation();
336    }
337
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
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
361    protected void hidePostCaptureAlert() {
362        if (mDecodeTaskForReview != null) {
363            mDecodeTaskForReview.cancel(true);
364        }
365        resumeFaceDetection();
366    }
367
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
376        public void onZoomValueChanged(float ratio) {
377            mController.onZoomChanged(ratio);
378        }
379
380        @Override
381        public void onZoomStart() {
382        }
383
384        @Override
385        public void onZoomEnd() {
386        }
387    }
388
389    public void setSwipingEnabled(boolean enable) {
390        mActivity.setSwipingEnabled(enable);
391    }
392
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
404    public void clearFaces() {
405        if (mFaceView != null) {
406            mFaceView.clear();
407        }
408    }
409
410    public void pauseFaceDetection() {
411        if (mFaceView != null) {
412            mFaceView.pause();
413        }
414    }
415
416    public void resumeFaceDetection() {
417        if (mFaceView != null) {
418            mFaceView.resume();
419        }
420    }
421
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
433    public void onFaceDetection(Face[] faces, CameraAgent.CameraProxy camera) {
434        if (mFaceView != null) {
435            mFaceView.setFaces(faces);
436        }
437    }
438
439}
440