PhotoUI.java revision 4efa8b54c1df4e06f2d3caed2568015a737f9dda
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.AlertDialog;
20import android.graphics.Bitmap;
21import android.graphics.Matrix;
22import android.graphics.SurfaceTexture;
23import android.hardware.Camera;
24import android.hardware.Camera.Face;
25import android.os.AsyncTask;
26import android.util.Log;
27import android.view.GestureDetector;
28import android.view.MotionEvent;
29import android.view.TextureView;
30import android.view.View;
31import android.view.View.OnClickListener;
32import android.view.ViewGroup;
33import android.view.ViewStub;
34import android.widget.Toast;
35
36import com.android.camera.FocusOverlayManager.FocusUI;
37import com.android.camera.app.CameraManager;
38import com.android.camera.settings.SettingsManager;
39import com.android.camera.ui.FaceView;
40import com.android.camera.ui.FocusIndicator;
41import com.android.camera.ui.ModeListView;
42import com.android.camera.ui.PreviewOverlay;
43import com.android.camera.ui.PreviewStatusListener;
44import com.android.camera.util.CameraUtil;
45import com.android.camera2.R;
46
47import java.util.List;
48
49public class PhotoUI implements
50    FocusUI, PreviewStatusListener,
51    CameraManager.CameraFaceDetectionCallback {
52
53    private static final String TAG = "PhotoUI";
54    private static final int DOWN_SAMPLE_FACTOR = 4;
55    private static final float UNSET = 0f;
56
57    private final PreviewOverlay mPreviewOverlay;
58    private CameraActivity mActivity;
59    private PhotoController mController;
60
61    private View mRootView;
62    private SurfaceTexture mSurfaceTexture;
63
64    private FaceView mFaceView;
65    private DecodeImageForReview mDecodeTaskForReview = null;
66    private Toast mNotSelectableToast;
67
68    private int mZoomMax;
69    private List<Integer> mZoomRatios;
70
71    private int mPreviewWidth = 0;
72    private int mPreviewHeight = 0;
73    private float mSurfaceTextureUncroppedWidth;
74    private float mSurfaceTextureUncroppedHeight;
75
76    private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
77    private TextureView mTextureView;
78    private Matrix mMatrix = null;
79    private float mAspectRatio = UNSET;
80    private final Object mSurfaceTextureLock = new Object();
81
82    private ButtonManager.ButtonCallback mCameraCallback;
83    private ButtonManager.ButtonCallback mHdrCallback;
84    private ButtonManager.ButtonCallback mRefocusCallback;
85
86    private final OnClickListener mCancelCallback = new OnClickListener() {
87        @Override
88        public void onClick(View v) {
89            mController.onCaptureCancelled();
90        }
91    };
92    private final OnClickListener mDoneCallback = new OnClickListener() {
93        @Override
94        public void onClick(View v) {
95            mController.onCaptureDone();
96        }
97    };
98    private final OnClickListener mRetakeCallback = new OnClickListener() {
99        @Override
100        public void onClick(View v) {
101            setupIntentToggleButtons();
102            mActivity.getCameraAppUI().transitionToIntentLayout();
103            mController.onCaptureRetake();
104        }
105    };
106
107    private final GestureDetector.OnGestureListener mPreviewGestureListener
108            = new GestureDetector.SimpleOnGestureListener() {
109        @Override
110        public boolean onSingleTapUp(MotionEvent ev) {
111            mController.onSingleTapUp(null, (int) ev.getX(), (int) ev.getY());
112            return true;
113        }
114    };
115
116    @Override
117    public GestureDetector.OnGestureListener getGestureListener() {
118        return mPreviewGestureListener;
119    }
120
121    public interface SurfaceTextureSizeChangedListener {
122        public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
123    }
124
125    @Override
126    public void onPreviewLayoutChanged(View v, int left, int top, int right,
127            int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
128        int width = right - left;
129        int height = bottom - top;
130        if (mPreviewWidth != width || mPreviewHeight != height) {
131            mPreviewWidth = width;
132            mPreviewHeight = height;
133        }
134    }
135
136    @Override
137    public boolean shouldAutoAdjustTransformMatrixOnLayout() {
138        return true;
139    }
140
141    @Override
142    public boolean shouldAutoAdjustBottomBar() {
143        return true;
144    }
145
146    private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
147        private final byte [] mData;
148        private int mOrientation;
149        private boolean mMirror;
150
151        public DecodeTask(byte[] data, int orientation, boolean mirror) {
152            mData = data;
153            mOrientation = orientation;
154            mMirror = mirror;
155        }
156
157        @Override
158        protected Bitmap doInBackground(Void... params) {
159            // Decode image in background.
160            Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR);
161            if (mOrientation != 0 || mMirror) {
162                Matrix m = new Matrix();
163                if (mMirror) {
164                    // Flip horizontally
165                    m.setScale(-1f, 1f);
166                }
167                m.preRotate(mOrientation);
168                return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m,
169                        false);
170            }
171            return bitmap;
172        }
173    }
174
175    private class DecodeImageForReview extends DecodeTask {
176        public DecodeImageForReview(byte[] data, int orientation, boolean mirror) {
177            super(data, orientation, mirror);
178        }
179
180        @Override
181        protected void onPostExecute(Bitmap bitmap) {
182            if (isCancelled()) {
183                return;
184            }
185            mDecodeTaskForReview = null;
186        }
187    }
188
189    public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
190        mActivity = activity;
191        mController = controller;
192        mRootView = parent;
193
194        ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
195        mActivity.getLayoutInflater().inflate(R.layout.photo_module,
196                 (ViewGroup) moduleRoot, true);
197        // display the view
198        mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
199        initIndicators();
200
201        mSurfaceTexture = mTextureView.getSurfaceTexture();
202
203        // Customize the bottom bar.
204        if (mActivity.getCurrentModuleIndex() == ModeListView.MODE_PHOTO) {
205            // Simple photo mode.
206            activity.getCameraAppUI().setBottomBarColor(
207                activity.getResources().getColor(R.color.camera_mode_color));
208        } else {
209            // Advanced photo mode.
210            activity.getCameraAppUI().setBottomBarColor(
211                activity.getResources().getColor(R.color.craft_mode_color));
212        }
213
214        ViewStub faceViewStub = (ViewStub) mRootView
215                .findViewById(R.id.face_view_stub);
216        if (faceViewStub != null) {
217            faceViewStub.inflate();
218            mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
219            setSurfaceTextureSizeChangedListener(mFaceView);
220        }
221
222        mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
223    }
224
225    public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) {
226        mSurfaceTextureSizeListener = listener;
227    }
228
229    public void updatePreviewAspectRatio(float aspectRatio) {
230        if (aspectRatio <= 0) {
231            Log.e(TAG, "Invalid aspect ratio: " + aspectRatio);
232            return;
233        }
234        if (aspectRatio < 1f) {
235            aspectRatio = 1f / aspectRatio;
236        }
237
238        if (mAspectRatio != aspectRatio) {
239            mAspectRatio = aspectRatio;
240            // Update transform matrix with the new aspect ratio.
241            mController.updatePreviewAspectRatio(mAspectRatio);
242        }
243    }
244
245    protected Object getSurfaceTextureLock() {
246        return mSurfaceTextureLock;
247    }
248
249    @Override
250    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
251        synchronized (mSurfaceTextureLock) {
252            Log.v(TAG, "SurfaceTexture ready.");
253            mSurfaceTexture = surface;
254            mController.onPreviewUIReady();
255        }
256    }
257
258    @Override
259    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
260        // Ignored, Camera does all the work for us
261    }
262
263    @Override
264    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
265        synchronized (mSurfaceTextureLock) {
266            mSurfaceTexture = null;
267            mController.onPreviewUIDestroyed();
268            Log.w(TAG, "SurfaceTexture destroyed");
269            return true;
270        }
271    }
272
273    @Override
274    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
275    }
276
277    public View getRootView() {
278        return mRootView;
279    }
280
281    private void initIndicators() {
282        // TODO init toggle buttons on bottom bar here
283    }
284
285    public void onCameraOpened(Camera.Parameters params,
286            ButtonManager.ButtonCallback cameraCallback,
287            ButtonManager.ButtonCallback hdrCallback,
288            ButtonManager.ButtonCallback refocusCallback) {
289        mCameraCallback = cameraCallback;
290        mHdrCallback = hdrCallback;
291        mRefocusCallback = refocusCallback;
292
293        if (mController.isImageCaptureIntent()) {
294            setupIntentToggleButtons();
295            mActivity.getCameraAppUI().transitionToIntentLayout();
296        } else {
297            setupToggleButtons();
298        }
299
300        initializeZoom(params);
301    }
302
303    public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
304        // Decode jpeg byte array and then animate the jpeg
305        DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
306        task.execute();
307    }
308
309    private void setupToggleButtons() {
310        ButtonManager buttonManager = mActivity.getButtonManager();
311        buttonManager.enableButton(ButtonManager.BUTTON_CAMERA, R.id.camera_toggle_button,
312            mCameraCallback, R.array.camera_id_icons);
313        buttonManager.enableButton(ButtonManager.BUTTON_FLASH, R.id.flash_toggle_button,
314            null, R.array.camera_flashmode_icons);
315
316        if (mActivity.getCurrentModuleIndex() == ModeListView.MODE_PHOTO) {
317            // Simple photo mode.
318            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS, R.id.hdr_plus_toggle_button);
319            buttonManager.hideButton(ButtonManager.BUTTON_REFOCUS, R.id.refocus_toggle_button);
320        } else {
321            // Advanced photo mode.
322            buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS, R.id.hdr_plus_toggle_button,
323                mHdrCallback, R.array.pref_camera_hdr_plus_icons);
324            buttonManager.enableButton(ButtonManager.BUTTON_REFOCUS, R.id.refocus_toggle_button,
325                mRefocusCallback, R.array.refocus_icons);
326        }
327    }
328
329    private void setupIntentToggleButtons() {
330        setupToggleButtons();
331        ButtonManager buttonManager = mActivity.getButtonManager();
332        buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL, R.id.cancel_button,
333                mCancelCallback);
334        buttonManager.enablePushButton(ButtonManager.BUTTON_DONE, R.id.done_button,
335                mDoneCallback);
336        buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE, R.id.retake_button,
337                mRetakeCallback);
338    }
339
340    public void initializeControlByIntent() {
341        if (mController.isImageCaptureIntent()) {
342            setupIntentToggleButtons();
343            mActivity.getCameraAppUI().transitionToIntentLayout();
344        }
345    }
346
347    // called from onResume but only the first time
348    public  void initializeFirstTime() {
349
350    }
351
352    // called from onResume every other time
353    public void initializeSecondTime(Camera.Parameters params) {
354        initializeZoom(params);
355        if (mController.isImageCaptureIntent()) {
356            hidePostCaptureAlert();
357        }
358        // Removes pie menu.
359    }
360
361    public void showLocationDialog() {
362        AlertDialog alert = mActivity.getFirstTimeLocationAlert();
363        alert.show();
364    }
365
366    public void initializeZoom(Camera.Parameters params) {
367        if ((params == null) || !params.isZoomSupported()) return;
368        mZoomMax = params.getMaxZoom();
369        mZoomRatios = params.getZoomRatios();
370        // Currently we use immediate zoom for fast zooming to get better UX and
371        // there is no plan to take advantage of the smooth zoom.
372        // TODO: Need to setup a path to AppUI to do this
373        mPreviewOverlay.setupZoom(mZoomMax, params.getZoom(), mZoomRatios, new ZoomChangeListener());
374    }
375
376    public void animateFlash() {
377        mController.startPreCaptureAnimation();
378    }
379
380    public boolean onBackPressed() {
381        // In image capture mode, back button should:
382        // 1) if there is any popup, dismiss them, 2) otherwise, get out of
383        // image capture
384        if (mController.isImageCaptureIntent()) {
385            mController.onCaptureCancelled();
386            return true;
387        } else if (!mController.isCameraIdle()) {
388            // ignore backs while we're taking a picture
389            return true;
390        } else {
391            return false;
392        }
393    }
394
395    protected void showCapturedImageForReview(byte[] jpegData, int orientation, boolean mirror) {
396        mDecodeTaskForReview = new DecodeImageForReview(jpegData, orientation, mirror);
397        mDecodeTaskForReview.execute();
398
399        setupIntentToggleButtons();
400        mActivity.getCameraAppUI().transitionToIntentReviewLayout();
401
402        pauseFaceDetection();
403    }
404
405    protected void hidePostCaptureAlert() {
406        if (mDecodeTaskForReview != null) {
407            mDecodeTaskForReview.cancel(true);
408        }
409        resumeFaceDetection();
410    }
411
412    public void setDisplayOrientation(int orientation) {
413        if (mFaceView != null) {
414            mFaceView.setDisplayOrientation(orientation);
415        }
416    }
417
418    // shutter button handling
419
420    public boolean isShutterPressed() {
421        return false;
422    }
423
424    /**
425     * Enables or disables the shutter button.
426     */
427    public void enableShutter(boolean enabled) {
428
429    }
430
431    public void pressShutterButton() {
432
433    }
434
435    private class ZoomChangeListener implements PreviewOverlay.OnZoomChangedListener {
436        @Override
437        public void onZoomValueChanged(int index) {
438            mController.onZoomChanged(index);
439        }
440
441        @Override
442        public void onZoomStart() {
443        }
444
445        @Override
446        public void onZoomEnd() {
447        }
448    }
449
450    public void setSwipingEnabled(boolean enable) {
451        mActivity.setSwipingEnabled(enable);
452    }
453
454    public SurfaceTexture getSurfaceTexture() {
455        return mSurfaceTexture;
456    }
457
458    public void onPause() {
459        if (mFaceView != null) mFaceView.clear();
460    }
461
462    // focus UI implementation
463    private FocusIndicator getFocusIndicator() {
464        return null;
465        //return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : null;
466    }
467
468    @Override
469    public boolean hasFaces() {
470        return (mFaceView != null && mFaceView.faceExists());
471    }
472
473    public void clearFaces() {
474        if (mFaceView != null) {
475            mFaceView.clear();
476        }
477    }
478
479    @Override
480    public void clearFocus() {
481        FocusIndicator indicator = getFocusIndicator();
482        if (indicator != null) {
483            indicator.clear();
484        }
485    }
486
487    @Override
488    public void setFocusPosition(int x, int y) {
489    }
490
491    @Override
492    public void onFocusStarted() {
493        FocusIndicator indicator = getFocusIndicator();
494        if (indicator != null) {
495            indicator.showStart();
496        }
497    }
498
499    @Override
500    public void onFocusSucceeded(boolean timeout) {
501        FocusIndicator indicator = getFocusIndicator();
502        if (indicator != null) {
503            indicator.showSuccess(timeout);
504        }
505    }
506
507    @Override
508    public void onFocusFailed(boolean timeout) {
509        FocusIndicator indicator = getFocusIndicator();
510        if (indicator != null) {
511            indicator.showFail(timeout);
512        }
513    }
514
515    @Override
516    public void pauseFaceDetection() {
517        if (mFaceView != null) mFaceView.pause();
518    }
519
520    @Override
521    public void resumeFaceDetection() {
522        if (mFaceView != null) mFaceView.resume();
523    }
524
525    public void onStartFaceDetection(int orientation, boolean mirror) {
526        mFaceView.clear();
527        mFaceView.setVisibility(View.VISIBLE);
528        mFaceView.setDisplayOrientation(orientation);
529        mFaceView.setMirror(mirror);
530        mFaceView.resume();
531    }
532
533    @Override
534    public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) {
535        mFaceView.setFaces(faces);
536    }
537
538}
539