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