CameraAppUI.java revision 7cbeceed6d03d0550797c435f3db6205714cd49f
1/*
2 * Copyright (C) 2013 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.app;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.Matrix;
22import android.graphics.SurfaceTexture;
23import android.hardware.display.DisplayManager;
24import android.util.Log;
25import android.view.GestureDetector;
26import android.view.LayoutInflater;
27import android.view.MotionEvent;
28import android.view.TextureView;
29import android.view.View;
30import android.view.ViewConfiguration;
31import android.view.ViewGroup;
32import android.widget.FrameLayout;
33
34import com.android.camera.AnimationManager;
35import com.android.camera.ButtonManager;
36import com.android.camera.ShutterButton;
37import com.android.camera.TextureViewHelper;
38import com.android.camera.filmstrip.FilmstripContentPanel;
39import com.android.camera.hardware.HardwareSpec;
40import com.android.camera.module.ModuleController;
41import com.android.camera.settings.SettingsManager;
42import com.android.camera.ui.BottomBar;
43import com.android.camera.ui.CaptureAnimationOverlay;
44import com.android.camera.ui.MainActivityLayout;
45import com.android.camera.ui.ModeListView;
46import com.android.camera.ui.ModeTransitionView;
47import com.android.camera.ui.PreviewOverlay;
48import com.android.camera.ui.PreviewStatusListener;
49import com.android.camera.util.ApiHelper;
50import android.util.CameraPerformanceTracker;
51import com.android.camera.util.CameraUtil;
52import com.android.camera.util.PhotoSphereHelper;
53import com.android.camera.util.UsageStatistics;
54import com.android.camera.widget.FilmstripLayout;
55import com.android.camera.widget.IndicatorIconController;
56import com.android.camera.widget.IndicatorOverlay;
57import com.android.camera2.R;
58
59import com.google.common.logging.eventprotos;
60
61/**
62 * CameraAppUI centralizes control of views shared across modules. Whereas module
63 * specific views will be handled in each Module UI. For example, we can now
64 * bring the flash animation and capture animation up from each module to app
65 * level, as these animations are largely the same for all modules.
66 *
67 * This class also serves to disambiguate touch events. It recognizes all the
68 * swipe gestures that happen on the preview by attaching a touch listener to
69 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
70 * of how swipe from each direction should be handled, it can then redirect these
71 * events to appropriate recipient views.
72 */
73public class CameraAppUI implements ModeListView.ModeSwitchListener,
74        TextureView.SurfaceTextureListener, ModeListView.ModeListOpenListener {
75
76    /**
77     * The bottom controls on the filmstrip.
78     */
79    public static interface BottomControls {
80        /** Values for the view state of the button. */
81        public final int VIEWER_NONE = 0;
82        public final int VIEWER_PHOTO_SPHERE = 1;
83        public final int VIEWER_REFOCUS = 2;
84        public final int VIEWER_OTHER = 3;
85
86        /**
87         * Sets a new or replaces an existing listener for bottom control events.
88         */
89        void setListener(Listener listener);
90
91        /**
92         * Set if the bottom controls are visible.
93         * @param visible {@code true} if visible.
94         */
95        void setVisible(boolean visible);
96
97        /**
98         * @param visible Whether the button is visible.
99         */
100        void setEditButtonVisibility(boolean visible);
101
102        /**
103         * @param enabled Whether the button is enabled.
104         */
105        void setEditEnabled(boolean enabled);
106
107        /**
108         * Sets the visibility of the view-photosphere button.
109         *
110         * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE},
111         *            {@link #VIEWER_REFOCUS}.
112         */
113        void setViewerButtonVisibility(int state);
114
115        /**
116         * @param enabled Whether the button is enabled.
117         */
118        void setViewEnabled(boolean enabled);
119
120        /**
121         * @param visible Whether the button is visible.
122         */
123        void setTinyPlanetButtonVisibility(boolean visible);
124
125        /**
126         * @param enabled Whether the button is enabled.
127         */
128        void setTinyPlanetEnabled(boolean enabled);
129
130        /**
131         * @param visible Whether the button is visible.
132         */
133        void setDeleteButtonVisibility(boolean visible);
134
135        /**
136         * @param enabled Whether the button is enabled.
137         */
138        void setDeleteEnabled(boolean enabled);
139
140        /**
141         * @param visible Whether the button is visible.
142         */
143        void setShareButtonVisibility(boolean visible);
144
145        /**
146         * @param enabled Whether the button is enabled.
147         */
148        void setShareEnabled(boolean enabled);
149
150        /**
151         * Classes implementing this interface can listen for events on the bottom
152         * controls.
153         */
154        public static interface Listener {
155            /**
156             * Called when the user pressed the "view" button to e.g. view a photo
157             * sphere or RGBZ image.
158             */
159            public void onExternalViewer();
160
161            /**
162             * Called when the "edit" button is pressed.
163             */
164            public void onEdit();
165
166            /**
167             * Called when the "tiny planet" button is pressed.
168             */
169            public void onTinyPlanet();
170
171            /**
172             * Called when the "delete" button is pressed.
173             */
174            public void onDelete();
175
176            /**
177             * Called when the "share" button is pressed.
178             */
179            public void onShare();
180        }
181    }
182
183    /**
184     * BottomBarUISpec provides a structure for modules
185     * to specify their ideal bottom bar mode options layout.
186     *
187     * Once constructed by a module, this class should be
188     * treated as read only.
189     *
190     * The application then edits this spec according to
191     * hardware limitations and displays the final bottom
192     * bar ui.
193     */
194    public static class BottomBarUISpec {
195        /** Mode options UI */
196
197        /**
198         * Set true if the camera option should be enabled.
199         * If not set or false, and multiple cameras are supported,
200         * the camera option will be disabled.
201         *
202         * If multiple cameras are not supported, this preference
203         * is ignored and the camera option will not be visible.
204         */
205        public boolean enableCamera;
206
207        /**
208         * Set true if the photo flash option should be enabled.
209         * If not set or false, the photo flash option will be
210         * disabled.
211         *
212         * If the hardware does not support multiple flash values,
213         * this preference is ignored and the flash option will
214         * be disabled.  It will not be made invisible in order to
215         * preserve a consistent experience across devices and between
216         * front and back cameras.
217         */
218        public boolean enableFlash;
219
220        /**
221         * Set true if the video flash option should be enabled.
222         * Same disable rules apply as the photo flash option.
223         */
224        public boolean enableTorchFlash;
225
226        /**
227         * Set true if the hdr/hdr+ option should be enabled.
228         * If not set or false, the hdr/hdr+ option will be disabled.
229         *
230         * Hdr or hdr+ will be chosen based on hardware limitations,
231         * with hdr+ prefered.
232         *
233         * If hardware supports neither hdr nor hdr+, then the hdr/hdr+
234         * will not be visible.
235         */
236        public boolean enableHdr;
237
238        /**
239         * Set true if hdr/hdr+ should not be visible, regardless of
240         * hardware limitations.
241         */
242        public boolean hideHdr;
243
244        /**
245         * Set true if the refocus option should be enabled.
246         * If not set or false, the refocus option will be disabled.
247         *
248         * This option is not constrained by hardware limitations.
249         */
250        public boolean enableRefocus;
251
252        /**
253         * Set true if refocus should not be visible.
254         */
255        public boolean hideRefocus;
256
257        /**
258         * Set true if the panorama horizontal option should be visible.
259         *
260         * This option is not constrained by hardware limitations.
261         */
262        public boolean enablePanoHorizontal;
263
264        /**
265         * Set true if the panorama vertical option should be visible.
266         *
267         * This option is not constrained by hardware limitations.
268         */
269        public boolean enablePanoVertical;
270
271        /** Intent UI */
272
273        /**
274         * Set true if the intent ui cancel option should be visible.
275         */
276        public boolean showCancel;
277        /**
278         * Set true if the intent ui done option should be visible.
279         */
280        public boolean showDone;
281        /**
282         * Set true if the intent ui retake option should be visible.
283         */
284        public boolean showRetake;
285        /**
286         * Set true if the intent ui review option should be visible.
287         */
288        public boolean showReview;
289
290        /** Mode options callbacks */
291
292        /**
293         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
294         * that will be executed when the camera option is pressed. This
295         * callback can be null.
296         */
297        public ButtonManager.ButtonCallback cameraCallback;
298
299        /**
300         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
301         * that will be executed when the flash option is pressed. This
302         * callback can be null.
303         */
304        public ButtonManager.ButtonCallback flashCallback;
305
306        /**
307         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
308         * that will be executed when the hdr/hdr+ option is pressed. This
309         * callback can be null.
310         */
311        public ButtonManager.ButtonCallback hdrCallback;
312
313        /**
314         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
315         * that will be executed when the refocus option is pressed. This
316         * callback can be null.
317         */
318        public ButtonManager.ButtonCallback refocusCallback;
319
320        /**
321         * A {@link android.view.View.OnClickListener} that will execute
322         * when the panorama horizontal option is pressed.
323         * This callback can be null.
324         */
325        public View.OnClickListener panoHorizontalCallback;
326
327        /**
328         * A {@link android.view.View.OnClickListener} that will execute
329         * when the panorama vertical option is pressed.
330         * This callback can be null.
331         */
332        public View.OnClickListener panoVerticalCallback;
333
334        /** Intent UI callbacks */
335
336        /**
337         * A {@link android.view.View.OnClickListener} that will execute
338         * when the cancel option is pressed. This callback can be null.
339         */
340        public View.OnClickListener cancelCallback;
341
342        /**
343         * A {@link android.view.View.OnClickListener} that will execute
344         * when the done option is pressed. This callback can be null.
345         */
346        public View.OnClickListener doneCallback;
347
348        /**
349         * A {@link android.view.View.OnClickListener} that will execute
350         * when the retake option is pressed. This callback can be null.
351         */
352        public View.OnClickListener retakeCallback;
353
354        /**
355         * A {@link android.view.View.OnClickListener} that will execute
356         * when the review option is pressed. This callback can be null.
357         */
358        public View.OnClickListener reviewCallback;
359    }
360
361
362    private final static String TAG = "CameraAppUI";
363
364    private final AppController mController;
365    private final boolean mIsCaptureIntent;
366    private final AnimationManager mAnimationManager;
367
368    // Swipe states:
369    private final static int IDLE = 0;
370    private final static int SWIPE_UP = 1;
371    private final static int SWIPE_DOWN = 2;
372    private final static int SWIPE_LEFT = 3;
373    private final static int SWIPE_RIGHT = 4;
374    private boolean mSwipeEnabled = true;
375
376    // Touch related measures:
377    private final int mSlop;
378    private final static int SWIPE_TIME_OUT_MS = 500;
379
380    private final static int SHIMMY_DELAY_MS = 1000;
381
382    // Mode cover states:
383    private final static int COVER_HIDDEN = 0;
384    private final static int COVER_SHOWN = 1;
385    private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
386    private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3;
387
388    // App level views:
389    private final FrameLayout mCameraRootView;
390    private final ModeTransitionView mModeTransitionView;
391    private final MainActivityLayout mAppRootView;
392    private final ModeListView mModeListView;
393    private final FilmstripLayout mFilmstripLayout;
394    private TextureView mTextureView;
395    private FrameLayout mModuleUI;
396    private BottomBar mBottomBar;
397    private IndicatorOverlay mIndicatorOverlay;
398    private boolean mShouldShowShimmy = false;
399    private IndicatorIconController mIndicatorIconController;
400
401    private TextureViewHelper mTextureViewHelper;
402    private final GestureDetector mGestureDetector;
403    private DisplayManager.DisplayListener mDisplayListener;
404    private int mLastRotation;
405    private int mSwipeState = IDLE;
406    private PreviewOverlay mPreviewOverlay;
407    private CaptureAnimationOverlay mCaptureOverlay;
408    private PreviewStatusListener mPreviewStatusListener;
409    private int mModeCoverState = COVER_HIDDEN;
410    private final FilmstripBottomControls mFilmstripBottomControls;
411    private final FilmstripContentPanel mFilmstripPanel;
412    private Runnable mHideCoverRunnable;
413    private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
414            = new View.OnLayoutChangeListener() {
415        @Override
416        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
417                int oldTop, int oldRight, int oldBottom) {
418            if (mPreviewStatusListener != null) {
419                mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
420                        oldTop, oldRight, oldBottom);
421            }
422        }
423    };
424
425    private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
426
427    public long getCoverHiddenTime() {
428        return mCoverHiddenTime;
429    }
430
431    public void updatePreviewAspectRatio(float aspectRatio) {
432        mTextureViewHelper.updateAspectRatio(aspectRatio);
433    }
434
435    /**
436     * This is to support modules that calculate their own transform matrix because
437     * they need to use a transform matrix to rotate the preview.
438     *
439     * @param matrix transform matrix to be set on the TextureView
440     */
441    public void updatePreviewTransform(Matrix matrix) {
442        mTextureViewHelper.updateTransform(matrix);
443    }
444
445    public interface AnimationFinishedListener {
446        public void onAnimationFinished(boolean success);
447    }
448
449    private class MyTouchListener implements View.OnTouchListener {
450        private boolean mScaleStarted = false;
451        @Override
452        public boolean onTouch(View v, MotionEvent event) {
453            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
454                mScaleStarted = false;
455            } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
456                mScaleStarted = true;
457            }
458            return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
459        }
460    }
461
462    /**
463     * This gesture listener finds out the direction of the scroll gestures and
464     * sends them to CameraAppUI to do further handling.
465     */
466    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
467        private MotionEvent mDown;
468
469        @Override
470        public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
471            if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
472                    || mSwipeState != IDLE
473                    || mIsCaptureIntent
474                    || !mSwipeEnabled) {
475                return false;
476            }
477
478            int deltaX = (int) (ev.getX() - mDown.getX());
479            int deltaY = (int) (ev.getY() - mDown.getY());
480            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
481                if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
482                    // Calculate the direction of the swipe.
483                    if (deltaX >= Math.abs(deltaY)) {
484                        // Swipe right.
485                        setSwipeState(SWIPE_RIGHT);
486                    } else if (deltaX <= -Math.abs(deltaY)) {
487                        // Swipe left.
488                        setSwipeState(SWIPE_LEFT);
489                    } else if (deltaY >= Math.abs(deltaX)) {
490                        // Swipe down.
491                        setSwipeState(SWIPE_DOWN);
492                    } else if (deltaY <= -Math.abs(deltaX)) {
493                        // Swipe up.
494                        setSwipeState(SWIPE_UP);
495                    }
496                }
497            }
498            return true;
499        }
500
501        private void setSwipeState(int swipeState) {
502            mSwipeState = swipeState;
503            // Notify new swipe detected.
504            onSwipeDetected(swipeState);
505        }
506
507        @Override
508        public boolean onDown(MotionEvent ev) {
509            mDown = MotionEvent.obtain(ev);
510            mSwipeState = IDLE;
511            return false;
512        }
513    }
514
515    public CameraAppUI(AppController controller, MainActivityLayout appRootView,
516            boolean isCaptureIntent) {
517        mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
518        mController = controller;
519        mIsCaptureIntent = isCaptureIntent;
520
521        mAppRootView = appRootView;
522        mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
523        mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
524        mModeTransitionView = (ModeTransitionView)
525                mAppRootView.findViewById(R.id.mode_transition_view);
526        mFilmstripBottomControls = new FilmstripBottomControls(
527                (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_controls));
528        mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
529        mGestureDetector = new GestureDetector(controller.getAndroidContext(),
530                new MyGestureListener());
531        mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
532        if (mModeListView != null) {
533            mModeListView.setModeSwitchListener(this);
534            mModeListView.setModeListOpenListener(this);
535        } else {
536            Log.e(TAG, "Cannot find mode list in the view hierarchy");
537        }
538        mAnimationManager = new AnimationManager();
539        initDisplayListener();
540    }
541
542    /**
543     * Enable or disable swipe gestures. We want to disable them e.g. while we
544     * record a video.
545     */
546    public void setSwipeEnabled(boolean enabled) {
547        mAppRootView.setSwipeEnabled(enabled);
548        mSwipeEnabled = enabled;
549    }
550
551    /**
552     * Initializes the display listener to listen to display changes such as
553     * 180-degree rotation change, which will not have an onConfigurationChanged
554     * callback.
555     */
556    private void initDisplayListener() {
557        if (ApiHelper.HAS_DISPLAY_LISTENER) {
558            mLastRotation = CameraUtil.getDisplayRotation(
559                    (Activity) mController.getAndroidContext());
560
561            mDisplayListener = new DisplayManager.DisplayListener() {
562                @Override
563                public void onDisplayAdded(int arg0) {
564                    // Do nothing.
565                }
566
567                @Override
568                public void onDisplayChanged(int displayId) {
569                    int rotation = CameraUtil.getDisplayRotation(
570                            (Activity) mController.getAndroidContext());
571                    if ((rotation - mLastRotation + 360) % 360 == 180
572                            && mPreviewStatusListener != null) {
573                        mPreviewStatusListener.onPreviewFlipped();
574                    }
575                    mLastRotation = rotation;
576                }
577
578                @Override
579                public void onDisplayRemoved(int arg0) {
580                    // Do nothing.
581                }
582            };
583
584            ((DisplayManager) mController.getAndroidContext()
585                    .getSystemService(Context.DISPLAY_SERVICE))
586                    .registerDisplayListener(mDisplayListener, null);
587        }
588    }
589
590    /**
591     * Redirects touch events to appropriate recipient views based on swipe direction.
592     * More specifically, swipe up and swipe down will be handled by the view that handles
593     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
594     * to mode list in order to bring up mode list.
595     */
596    private void onSwipeDetected(int swipeState) {
597        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
598            // Quick switch between modes.
599            int currentModuleIndex = mController.getCurrentModuleIndex();
600            final int moduleToTransitionTo =
601                    mController.getQuickSwitchToModuleId(currentModuleIndex);
602            if (currentModuleIndex != moduleToTransitionTo) {
603                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
604
605                int shadeColorId = CameraUtil.getCameraThemeColorId(moduleToTransitionTo,
606                        mController.getAndroidContext());
607                int iconRes = CameraUtil.getCameraModeIconResId(moduleToTransitionTo,
608                        mController.getAndroidContext());
609
610                AnimationFinishedListener listener = new AnimationFinishedListener() {
611                    @Override
612                    public void onAnimationFinished(boolean success) {
613                        if (success) {
614                            mHideCoverRunnable = new Runnable() {
615                                @Override
616                                public void run() {
617                                    mModeTransitionView.startPeepHoleAnimation();
618                                }
619                            };
620                            mModeCoverState = COVER_SHOWN;
621                            // Go to new module when the previous operation is successful.
622                            mController.onModeSelected(moduleToTransitionTo);
623                        }
624                    }
625                };
626                if (mSwipeState == SWIPE_UP) {
627                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
628                } else {
629                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
630                }
631            }
632        } else if (swipeState == SWIPE_LEFT) {
633            // Pass the touch sequence to filmstrip layout.
634            UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.FILMSTRIP,
635                eventprotos.CameraEvent.InteractionCause.SWIPE_LEFT);
636            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
637        } else if (swipeState == SWIPE_RIGHT) {
638            // Pass the touch to mode switcher
639            mAppRootView.redirectTouchEventsTo(mModeListView);
640        }
641    }
642
643    /**
644     * Gets called when activity resumes in preview.
645     */
646    public void resume() {
647        if (mTextureView == null || mTextureView.getSurfaceTexture() != null) {
648            if (!mIsCaptureIntent) {
649                showShimmyDelayed();
650            }
651        } else {
652            // Show mode theme cover until preview is ready
653            showModeCoverUntilPreviewReady();
654        }
655        // Hide action bar first since we are in full screen mode first, and
656        // switch the system UI to lights-out mode.
657        mFilmstripPanel.hide();
658    }
659
660    /**
661     * A cover view showing the mode theme color and mode icon will be visible on
662     * top of preview until preview is ready (i.e. camera preview is started and
663     * the first frame has been received).
664     */
665    private void showModeCoverUntilPreviewReady() {
666        int modeId = mController.getCurrentModuleIndex();
667        int colorId = CameraUtil.getCameraThemeColorId(modeId, mController.getAndroidContext());
668        int iconId = CameraUtil.getCameraModeIconResId(modeId, mController.getAndroidContext());
669        mModeTransitionView.setupModeCover(colorId, iconId);
670        mHideCoverRunnable = new Runnable() {
671            @Override
672            public void run() {
673                mModeTransitionView.hideModeCover(new AnimationFinishedListener() {
674                    @Override
675                    public void onAnimationFinished(boolean success) {
676                        if (success) {
677                            showShimmyDelayed();
678                        }
679                    }
680                });
681            }
682        };
683        mModeCoverState = COVER_SHOWN;
684    }
685
686    private void showShimmyDelayed() {
687        if (!mIsCaptureIntent) {
688            // Show shimmy in SHIMMY_DELAY_MS
689            mShouldShowShimmy = mController.shouldShowShimmy();
690            if (mShouldShowShimmy) {
691                mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
692            }
693        }
694    }
695
696    private void hideModeCover() {
697        if (mHideCoverRunnable != null) {
698            mAppRootView.post(mHideCoverRunnable);
699            mHideCoverRunnable = null;
700        }
701        mModeCoverState = COVER_HIDDEN;
702        if (mCoverHiddenTime < 0) {
703            mCoverHiddenTime = System.currentTimeMillis();
704        }
705    }
706
707    @Override
708    public void onOpenFullScreen() {
709        if (mShouldShowShimmy) {
710            mController.decrementShimmyPlayTimes();
711            // Sets should show shimmy flag to false for this session (i.e. until
712            // next onResume)
713            mShouldShowShimmy = false;
714        }
715    }
716
717    /**
718     * Called when the back key is pressed.
719     *
720     * @return Whether the UI responded to the key event.
721     */
722    public boolean onBackPressed() {
723        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
724            return mFilmstripLayout.onBackPressed();
725        } else {
726            return mModeListView.onBackPressed();
727        }
728    }
729
730    /**
731     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
732     * listens to SurfaceTexture changes. In addition, listeners are set on
733     * dependent app ui elements.
734     *
735     * @param previewStatusListener the listener that gets notified when SurfaceTexture
736     *                              changes
737     */
738    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
739        mPreviewStatusListener = previewStatusListener;
740        if (mPreviewStatusListener != null) {
741            onPreviewListenerChanged();
742        }
743    }
744
745    /**
746     * When the PreviewStatusListener changes, listeners need to be
747     * set on the following app ui elements:
748     * {@link com.android.camera.ui.PreviewOverlay},
749     * {@link com.android.camera.ui.BottomBar},
750     * {@link com.android.camera.ui.IndicatorOverlay},
751     * {@link com.android.camera.ui.IndicatorIconController}.
752     */
753    private void onPreviewListenerChanged() {
754        // Set a listener for recognizing preview gestures.
755        GestureDetector.OnGestureListener gestureListener
756            = mPreviewStatusListener.getGestureListener();
757        if (gestureListener != null) {
758            mPreviewOverlay.setGestureListener(gestureListener);
759        }
760
761        // Set a listener for resizing the bottom bar on
762        // preview size changes.
763        mTextureViewHelper.setAutoAdjustTransform(
764            mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
765        if (mPreviewStatusListener.shouldAutoAdjustBottomBar()) {
766            mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
767            mTextureViewHelper.addPreviewAreaSizeChangedListener(mBottomBar);
768        }
769
770        // Set a listener for resizing the indicator overlay on
771        // preview size changes.
772        mIndicatorOverlay = (IndicatorOverlay) mAppRootView.findViewById(
773            R.id.indicator_overlay);
774        mTextureViewHelper.addPreviewAreaSizeChangedListener(mIndicatorOverlay);
775
776        if (mIndicatorIconController == null) {
777            mIndicatorIconController =
778                new IndicatorIconController(mController, mAppRootView);
779        }
780        mController.getSettingsManager().addListener(mIndicatorIconController);
781    }
782
783    /**
784     * This method should be called in onCameraOpened.  It defines CameraAppUI
785     * specific changes that depend on the camera or camera settings.
786     */
787    public void onChangeCamera() {
788        ModuleController moduleController = mController.getCurrentModuleController();
789        if (moduleController.isUsingBottomBar()) {
790            applyModuleSpecs(moduleController.getHardwareSpec(),
791                moduleController.getBottomBarSpec());
792        }
793
794        if (mIndicatorIconController != null) {
795            // Sync the settings state with the indicator state.
796            mIndicatorIconController.syncIndicators();
797        }
798    }
799
800    /**
801     * Adds a listener to receive callbacks when preview area size changes.
802     */
803    public void addPreviewAreaSizeChangedListener(
804            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
805        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
806    }
807
808    /**
809     * Removes a listener that receives callbacks when preview area size changes.
810     */
811    public void removePreviewAreaSizeChangedListener(
812            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
813        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
814    }
815
816    /**
817     * This inflates generic_module layout, which contains all the shared views across
818     * modules. Then each module inflates their own views in the given view group. For
819     * now, this is called every time switching from a not-yet-refactored module to a
820     * refactored module. In the future, this should only need to be done once per app
821     * start.
822     */
823    public void prepareModuleUI() {
824        mCameraRootView.removeAllViews();
825        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
826                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
827        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
828
829        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
830        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
831        mTextureViewHelper = new TextureViewHelper(mTextureView);
832        mTextureViewHelper.setSurfaceTextureListener(this);
833        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
834
835        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
836        mBottomBar.setupToggle(mIsCaptureIntent);
837
838        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
839        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
840        mPreviewOverlay.setOnPreviewTouchedListener(mBottomBar);
841
842        mCaptureOverlay = (CaptureAnimationOverlay)
843                mCameraRootView.findViewById(R.id.capture_overlay);
844        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
845        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
846
847        if (mIndicatorIconController == null) {
848            mIndicatorIconController =
849                new IndicatorIconController(mController, mAppRootView);
850        }
851
852        mController.getButtonManager().load(mCameraRootView);
853        mController.getButtonManager().setListener(mIndicatorIconController);
854    }
855
856    // TODO: Remove this when refactor is done.
857    // This is here to ensure refactored modules can work with not-yet-refactored ones.
858    public void clearCameraUI() {
859        mCameraRootView.removeAllViews();
860        mModuleUI = null;
861        mTextureView = null;
862        mPreviewOverlay = null;
863        mBottomBar = null;
864        mIndicatorOverlay = null;
865        mIndicatorIconController = null;
866        setBottomBarShutterListener(null);
867    }
868
869    /**
870     * Called indirectly from each module in their initialization to get a view group
871     * to inflate the module specific views in.
872     *
873     * @return a view group for modules to attach views to
874     */
875    public FrameLayout getModuleRootView() {
876        // TODO: Change it to mModuleUI when refactor is done
877        return mCameraRootView;
878    }
879
880    /**
881     * Remove all the module specific views.
882     */
883    public void clearModuleUI() {
884        if (mModuleUI != null) {
885            mModuleUI.removeAllViews();
886        }
887        mTextureViewHelper.addPreviewAreaSizeChangedListener(null);
888
889        mPreviewStatusListener = null;
890        mPreviewOverlay.reset();
891    }
892
893    /**
894     * Gets called when preview is ready to start. It sets up one shot preview callback
895     * in order to receive a callback when the preview frame is available, so that
896     * the preview cover can be hidden to reveal preview.
897     *
898     * An alternative for getting the timing to hide preview cover is through
899     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
900     * which is less accurate but therefore is the fallback for modules that manage
901     * their own preview callbacks (as setting one preview callback will override
902     * any other installed preview callbacks), or use camera2 API.
903     */
904    public void onPreviewReadyToStart() {
905        if (mModeCoverState == COVER_SHOWN) {
906            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
907            mController.setupOneShotPreviewListener();
908        }
909    }
910
911    /**
912     * Gets called when preview is started.
913     */
914    public void onPreviewStarted() {
915        if (mModeCoverState == COVER_SHOWN) {
916            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
917        }
918    }
919
920    /**
921     * Gets notified when next preview frame comes in.
922     */
923    public void onNewPreviewFrame() {
924        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
925        hideModeCover();
926        mModeCoverState = COVER_HIDDEN;
927    }
928
929    /**
930     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
931     *
932     * @param modeIndex mode index of the selected mode
933     */
934    @Override
935    public void onModeSelected(int modeIndex) {
936        mHideCoverRunnable = new Runnable() {
937            @Override
938            public void run() {
939                mModeListView.startModeSelectionAnimation();
940            }
941        };
942        mModeCoverState = COVER_SHOWN;
943
944        int lastIndex = mController.getCurrentModuleIndex();
945        mController.onModeSelected(modeIndex);
946        int currentIndex = mController.getCurrentModuleIndex();
947
948        if (mTextureView == null) {
949            // TODO: Remove this when all the modules use TextureView
950            int temporaryDelay = 600; // ms
951            mModeListView.postDelayed(new Runnable() {
952                @Override
953                public void run() {
954                    hideModeCover();
955                }
956            }, temporaryDelay);
957        } else if (lastIndex == currentIndex) {
958            hideModeCover();
959        }
960    }
961
962    /********************** Capture animation **********************/
963    /* TODO: This session is subject to UX changes. In addition to the generic
964       flash animation and post capture animation, consider designating a parameter
965       for specifying the type of animation, as well as an animation finished listener
966       so that modules can have more knowledge of the status of the animation. */
967
968    /**
969     * Starts the pre-capture animation.
970     */
971    public void startPreCaptureAnimation() {
972        mCaptureOverlay.startFlashAnimation();
973    }
974
975    /**
976     * Cancels the pre-capture animation.
977     */
978    public void cancelPreCaptureAnimation() {
979        mAnimationManager.cancelAnimations();
980    }
981
982    /**
983     * Cancels the post-capture animation.
984     */
985    public void cancelPostCaptureAnimation() {
986        mAnimationManager.cancelAnimations();
987    }
988
989    public FilmstripContentPanel getFilmstripContentPanel() {
990        return mFilmstripPanel;
991    }
992
993    /**
994     * @return The {@link com.android.camera.app.CameraAppUI.BottomControls} on the
995     * bottom of the filmstrip.
996     */
997    public BottomControls getFilmstripBottomControls() {
998        return mFilmstripBottomControls;
999    }
1000
1001    /**
1002     * @param listener The listener for bottom controls.
1003     */
1004    public void setFilmstripBottomControlsListener(BottomControls.Listener listener) {
1005        mFilmstripBottomControls.setListener(listener);
1006    }
1007
1008    /***************************SurfaceTexture Listener*********************************/
1009
1010    @Override
1011    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1012        Log.v(TAG, "SurfaceTexture is available");
1013        if (mPreviewStatusListener != null) {
1014            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1015        }
1016    }
1017
1018    @Override
1019    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1020        if (mPreviewStatusListener != null) {
1021            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1022        }
1023    }
1024
1025    @Override
1026    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1027        Log.v(TAG, "SurfaceTexture is destroyed");
1028        if (mPreviewStatusListener != null) {
1029            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1030        }
1031        return false;
1032    }
1033
1034    @Override
1035    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1036        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1037            CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1038            hideModeCover();
1039            mModeCoverState = COVER_HIDDEN;
1040        }
1041        if (mPreviewStatusListener != null) {
1042            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1043        }
1044    }
1045
1046    /**
1047     * Sets the color of the bottom bar.
1048     */
1049    public void setBottomBarColor(int colorId) {
1050        mBottomBar.setBackgroundColor(colorId);
1051    }
1052
1053    /**
1054     * Sets the pressed color of the bottom bar.
1055     */
1056    public void setBottomBarPressedColor(int colorId) {
1057        mBottomBar.setBackgroundPressedColor(colorId);
1058    }
1059
1060    // TODO: refactor this out so it can controlled by the app.
1061    /**
1062     * Sets the shutter button icon on the bottom bar
1063     */
1064    public void setBottomBarShutterIcon(int shutterIconId) {
1065        mBottomBar.setShutterButtonIcon(shutterIconId);
1066    }
1067
1068    public void animateBottomBarToCircle(int shutterIconId) {
1069        mBottomBar.animateToCircle(shutterIconId);
1070    }
1071
1072    public void animateBottomBarToFullSize(int shutterIconId) {
1073        mBottomBar.animateToFullSize(shutterIconId);
1074    }
1075
1076    /**
1077     * Set the visibility of the bottom bar.
1078     */
1079    // TODO: needed for when panorama is managed by the generic module ui.
1080    public void setBottomBarVisible(boolean visible) {
1081        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1082    }
1083
1084    /**
1085     * If the bottom bar is visible (hence has been drawn),
1086     * this sets a {@link #ShutterButton.OnShutterButtonListener}
1087     * on the global shutter button,
1088     */
1089    public void setBottomBarShutterListener(
1090            ShutterButton.OnShutterButtonListener listener) {
1091        ShutterButton shutterButton
1092            = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1093        if (shutterButton != null) {
1094            shutterButton.setOnShutterButtonListener(listener);
1095        }
1096    }
1097
1098    /**
1099     * Performs a transition to the global intent layout.
1100     */
1101    public void transitionToIntentLayout() {
1102        ModuleController moduleController = mController.getCurrentModuleController();
1103        if (moduleController.isUsingBottomBar()) {
1104            applyModuleSpecs(moduleController.getHardwareSpec(),
1105                moduleController.getBottomBarSpec());
1106            mBottomBar.transitionToIntentLayout();
1107        }
1108    }
1109
1110    /**
1111     * Performs a transition to the global intent review layout.
1112     */
1113    public void transitionToIntentReviewLayout() {
1114        ModuleController moduleController = mController.getCurrentModuleController();
1115        if (moduleController.isUsingBottomBar()) {
1116            applyModuleSpecs(moduleController.getHardwareSpec(),
1117                moduleController.getBottomBarSpec());
1118            mBottomBar.transitionToIntentReviewLayout();
1119        }
1120    }
1121
1122    /**
1123     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1124     * to the bottom bar mode options based on limitations from a
1125     * {@link com.android.camera.hardware.HardwareSpec}.
1126     *
1127     * Options not supported by the hardware are either hidden
1128     * or disabled, depending on the option.
1129     *
1130     * Otherwise, the option is fully enabled and clickable.
1131     */
1132    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1133           final BottomBarUISpec bottomBarSpec) {
1134        if (hardwareSpec == null || bottomBarSpec == null) {
1135            return;
1136        }
1137
1138        ButtonManager buttonManager = mController.getButtonManager();
1139        SettingsManager settingsManager = mController.getSettingsManager();
1140
1141        /** Standard mode options */
1142        if (hardwareSpec.isFrontCameraSupported()) {
1143            if (bottomBarSpec.enableCamera) {
1144                buttonManager.enableButton(ButtonManager.BUTTON_CAMERA,
1145                    bottomBarSpec.cameraCallback);
1146            } else {
1147                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1148            }
1149        } else {
1150            // Hide camera icon if front camera not available.
1151            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1152        }
1153
1154        if (hardwareSpec.isFlashSupported()) {
1155            if (bottomBarSpec.enableFlash && settingsManager.isCameraBackFacing()) {
1156                buttonManager.enableButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1157            } else if (bottomBarSpec.enableTorchFlash && settingsManager.isCameraBackFacing()) {
1158                buttonManager.enableButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1159            } else {
1160                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1161            }
1162        } else {
1163            // Disable flash icon if not supported by the hardware.
1164            buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1165        }
1166
1167        if (bottomBarSpec.hideHdr) {
1168            // Force hide hdr or hdr plus icon.
1169            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1170        } else {
1171            if (hardwareSpec.isHdrPlusSupported()) {
1172                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1173                    buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS,
1174                        bottomBarSpec.hdrCallback);
1175                } else {
1176                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1177                }
1178            } else if (hardwareSpec.isHdrSupported()) {
1179                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1180                    buttonManager.enableButton(ButtonManager.BUTTON_HDR,
1181                        bottomBarSpec.hdrCallback);
1182                } else {
1183                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1184                }
1185            } else {
1186                // Hide hdr plus or hdr icon if neither are supported.
1187                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1188            }
1189        }
1190
1191        if (bottomBarSpec.hideRefocus) {
1192            buttonManager.hideButton(ButtonManager.BUTTON_REFOCUS);
1193        } else {
1194            if (bottomBarSpec.enableRefocus) {
1195                buttonManager.enableButton(ButtonManager.BUTTON_REFOCUS,
1196                    bottomBarSpec.refocusCallback);
1197            } else {
1198                // Disable refocus icon when not enabled, not dependent
1199                // on hardware spec.
1200                buttonManager.disableButton(ButtonManager.BUTTON_REFOCUS);
1201            }
1202        }
1203
1204        if (bottomBarSpec.enablePanoHorizontal
1205                && PhotoSphereHelper.getPanoramaHorizontalDrawableId() > 0) {
1206            buttonManager.enablePushButton(ButtonManager.BUTTON_PANO_HORIZONTAL,
1207                bottomBarSpec.panoHorizontalCallback,
1208                PhotoSphereHelper.getPanoramaHorizontalDrawableId());
1209        }
1210
1211        if (bottomBarSpec.enablePanoVertical
1212                && PhotoSphereHelper.getPanoramaVerticalDrawableId() > 0) {
1213            buttonManager.enablePushButton(ButtonManager.BUTTON_PANO_VERTICAL,
1214                bottomBarSpec.panoVerticalCallback,
1215                PhotoSphereHelper.getPanoramaVerticalDrawableId());
1216        }
1217
1218        /** Intent UI */
1219        if (bottomBarSpec.showCancel) {
1220            buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL,
1221                bottomBarSpec.cancelCallback);
1222        }
1223        if (bottomBarSpec.showDone) {
1224            buttonManager.enablePushButton(ButtonManager.BUTTON_DONE,
1225                bottomBarSpec.doneCallback);
1226        }
1227        if (bottomBarSpec.showRetake) {
1228            buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE,
1229                bottomBarSpec.retakeCallback);
1230        }
1231    }
1232}
1233