CameraAppUI.java revision 791f211c87d0e15e3264086a5ea75ff9066aa177
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.content.Context;
20import android.content.res.Configuration;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Matrix;
24import android.graphics.RectF;
25import android.graphics.SurfaceTexture;
26import android.hardware.display.DisplayManager;
27import android.util.CameraPerformanceTracker;
28import android.util.Log;
29import android.view.GestureDetector;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.TextureView;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewGroup;
36import android.widget.FrameLayout;
37
38import com.android.camera.AnimationManager;
39import com.android.camera.ButtonManager;
40import com.android.camera.ShutterButton;
41import com.android.camera.TextureViewHelper;
42import com.android.camera.filmstrip.FilmstripContentPanel;
43import com.android.camera.hardware.HardwareSpec;
44import com.android.camera.module.ModuleController;
45import com.android.camera.settings.SettingsManager;
46import com.android.camera.ui.BottomBar;
47import com.android.camera.ui.CaptureAnimationOverlay;
48import com.android.camera.ui.GridLines;
49import com.android.camera.ui.MainActivityLayout;
50import com.android.camera.ui.ModeListView;
51import com.android.camera.ui.ModeTransitionView;
52import com.android.camera.ui.PreviewOverlay;
53import com.android.camera.ui.PreviewStatusListener;
54import com.android.camera.util.ApiHelper;
55import com.android.camera.util.CameraUtil;
56import com.android.camera.util.Gusterpolator;
57import com.android.camera.util.PhotoSphereHelper;
58import com.android.camera.util.UsageStatistics;
59import com.android.camera.widget.FilmstripLayout;
60import com.android.camera.widget.IndicatorIconController;
61import com.android.camera.widget.ModeOptionsOverlay;
62import com.android.camera.widget.PeekView;
63import com.android.camera2.R;
64import com.google.common.logging.eventprotos;
65
66/**
67 * CameraAppUI centralizes control of views shared across modules. Whereas module
68 * specific views will be handled in each Module UI. For example, we can now
69 * bring the flash animation and capture animation up from each module to app
70 * level, as these animations are largely the same for all modules.
71 *
72 * This class also serves to disambiguate touch events. It recognizes all the
73 * swipe gestures that happen on the preview by attaching a touch listener to
74 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
75 * of how swipe from each direction should be handled, it can then redirect these
76 * events to appropriate recipient views.
77 */
78public class CameraAppUI implements ModeListView.ModeSwitchListener,
79        TextureView.SurfaceTextureListener, ModeListView.ModeListOpenListener {
80
81    /**
82     * The bottom controls on the filmstrip.
83     */
84    public static interface BottomControls {
85        /** Values for the view state of the button. */
86        public final int VIEWER_NONE = 0;
87        public final int VIEWER_PHOTO_SPHERE = 1;
88        public final int VIEWER_REFOCUS = 2;
89        public final int VIEWER_OTHER = 3;
90
91        /**
92         * Sets a new or replaces an existing listener for bottom control events.
93         */
94        void setListener(Listener listener);
95
96        /**
97         * Set if the bottom controls are visible.
98         * @param visible {@code true} if visible.
99         */
100        void setVisible(boolean visible);
101
102        /**
103         * @param visible Whether the button is visible.
104         */
105        void setEditButtonVisibility(boolean visible);
106
107        /**
108         * @param enabled Whether the button is enabled.
109         */
110        void setEditEnabled(boolean enabled);
111
112        /**
113         * Sets the visibility of the view-photosphere button.
114         *
115         * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE},
116         *            {@link #VIEWER_REFOCUS}.
117         */
118        void setViewerButtonVisibility(int state);
119
120        /**
121         * @param enabled Whether the button is enabled.
122         */
123        void setViewEnabled(boolean enabled);
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 camera option should not be visible, regardless
209         * of hardware limitations.
210         */
211        public boolean hideCamera;
212
213        /**
214         * Set true if the photo flash option should be enabled.
215         * If not set or false, the photo flash option will be
216         * disabled.
217         *
218         * If the hardware does not support multiple flash values,
219         * this preference is ignored and the flash option will
220         * be disabled.  It will not be made invisible in order to
221         * preserve a consistent experience across devices and between
222         * front and back cameras.
223         */
224        public boolean enableFlash;
225
226        /**
227         * Set true if the video flash option should be enabled.
228         * Same disable rules apply as the photo flash option.
229         */
230        public boolean enableTorchFlash;
231
232        /**
233         * Set true if flash should not be visible, regardless of
234         * hardware limitations.
235         */
236        public boolean hideFlash;
237
238        /**
239         * Set true if the hdr/hdr+ option should be enabled.
240         * If not set or false, the hdr/hdr+ option will be disabled.
241         *
242         * Hdr or hdr+ will be chosen based on hardware limitations,
243         * with hdr+ prefered.
244         *
245         * If hardware supports neither hdr nor hdr+, then the hdr/hdr+
246         * will not be visible.
247         */
248        public boolean enableHdr;
249
250        /**
251         * Set true if hdr/hdr+ should not be visible, regardless of
252         * hardware limitations.
253         */
254        public boolean hideHdr;
255
256        /**
257         * Set true if grid lines should be visible.  Not setting this
258         * causes grid lines to be disabled.  This option is agnostic to
259         * the hardware.
260         */
261        public boolean enableGridLines;
262
263        /**
264         * Set true if grid lines should not be visible.
265         */
266        public boolean hideGridLines;
267
268        /**
269         * Set true if the panorama orientation option should be visible.
270         *
271         * This option is not constrained by hardware limitations.
272         */
273        public boolean enablePanoOrientation;
274
275        /** Intent UI */
276
277        /**
278         * Set true if the intent ui cancel option should be visible.
279         */
280        public boolean showCancel;
281        /**
282         * Set true if the intent ui done option should be visible.
283         */
284        public boolean showDone;
285        /**
286         * Set true if the intent ui retake option should be visible.
287         */
288        public boolean showRetake;
289        /**
290         * Set true if the intent ui review option should be visible.
291         */
292        public boolean showReview;
293
294        /** Mode options callbacks */
295
296        /**
297         * A {@link com.android.camera.ButtonManager.ButtonCallback}
298         * that will be executed when the camera option is pressed. This
299         * callback can be null.
300         */
301        public ButtonManager.ButtonCallback cameraCallback;
302
303        /**
304         * A {@link com.android.camera.ButtonManager.ButtonCallback}
305         * that will be executed when the flash option is pressed. This
306         * callback can be null.
307         */
308        public ButtonManager.ButtonCallback flashCallback;
309
310        /**
311         * A {@link com.android.camera.ButtonManager.ButtonCallback}
312         * that will be executed when the hdr/hdr+ option is pressed. This
313         * callback can be null.
314         */
315        public ButtonManager.ButtonCallback hdrCallback;
316
317        /**
318         * A {@link com.android.camera.ButtonManager.ButtonCallback}
319         * that will be executed when the grid lines option is pressed. This
320         * callback can be null.
321         */
322        public ButtonManager.ButtonCallback gridLinesCallback;
323
324        /**
325         * A {@link com.android.camera.ButtonManager.ButtonCallback}
326         * that will execute when the panorama orientation option is pressed.
327         * This callback can be null.
328         */
329        public ButtonManager.ButtonCallback panoOrientationCallback;
330
331        /** Intent UI callbacks */
332
333        /**
334         * A {@link android.view.View.OnClickListener} that will execute
335         * when the cancel option is pressed. This callback can be null.
336         */
337        public View.OnClickListener cancelCallback;
338
339        /**
340         * A {@link android.view.View.OnClickListener} that will execute
341         * when the done option is pressed. This callback can be null.
342         */
343        public View.OnClickListener doneCallback;
344
345        /**
346         * A {@link android.view.View.OnClickListener} that will execute
347         * when the retake option is pressed. This callback can be null.
348         */
349        public View.OnClickListener retakeCallback;
350
351        /**
352         * A {@link android.view.View.OnClickListener} that will execute
353         * when the review option is pressed. This callback can be null.
354         */
355        public View.OnClickListener reviewCallback;
356    }
357
358
359    private final static String TAG = "CameraAppUI";
360
361    private final AppController mController;
362    private final boolean mIsCaptureIntent;
363    private final AnimationManager mAnimationManager;
364
365    // Swipe states:
366    private final static int IDLE = 0;
367    private final static int SWIPE_UP = 1;
368    private final static int SWIPE_DOWN = 2;
369    private final static int SWIPE_LEFT = 3;
370    private final static int SWIPE_RIGHT = 4;
371    private boolean mSwipeEnabled = true;
372
373    // Shared Surface Texture properities.
374    private SurfaceTexture mSurface;
375    private int mSurfaceWidth;
376    private int mSurfaceHeight;
377
378    // Touch related measures:
379    private final int mSlop;
380    private final static int SWIPE_TIME_OUT_MS = 500;
381
382    private final static int SHIMMY_DELAY_MS = 1000;
383
384    // Mode cover states:
385    private final static int COVER_HIDDEN = 0;
386    private final static int COVER_SHOWN = 1;
387    private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
388    private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3;
389
390    // App level views:
391    private final FrameLayout mCameraRootView;
392    private final ModeTransitionView mModeTransitionView;
393    private final MainActivityLayout mAppRootView;
394    private final ModeListView mModeListView;
395    private final FilmstripLayout mFilmstripLayout;
396    private TextureView mTextureView;
397    private FrameLayout mModuleUI;
398    private BottomBar mBottomBar;
399    private ModeOptionsOverlay mModeOptionsOverlay;
400    private boolean mShouldShowShimmy = false;
401    private IndicatorIconController mIndicatorIconController;
402    private View mFocusOverlay;
403
404    private TextureViewHelper mTextureViewHelper;
405    private final GestureDetector mGestureDetector;
406    private DisplayManager.DisplayListener mDisplayListener;
407    private int mLastRotation;
408    private int mSwipeState = IDLE;
409    private PreviewOverlay mPreviewOverlay;
410    private GridLines mGridLines;
411    private CaptureAnimationOverlay mCaptureOverlay;
412    private PreviewStatusListener mPreviewStatusListener;
413    private int mModeCoverState = COVER_HIDDEN;
414    private final FilmstripBottomControls mFilmstripBottomControls;
415    private final FilmstripContentPanel mFilmstripPanel;
416    private Runnable mHideCoverRunnable;
417    private final UncoveredPreviewAreaSizeChangedListener mUncoverPreviewAreaChangedListener;
418    private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
419            = new View.OnLayoutChangeListener() {
420        @Override
421        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
422                int oldTop, int oldRight, int oldBottom) {
423            if (mPreviewStatusListener != null) {
424                mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
425                        oldTop, oldRight, oldBottom);
426            }
427        }
428    };
429    private View mModeOptionsToggle;
430    private RectF mBottomBarRect = new RectF();
431    private final View.OnLayoutChangeListener mBottomBarLayoutChangeListener
432            = new View.OnLayoutChangeListener() {
433        @Override
434        public void onLayoutChange(View v, int left, int top, int right, int bottom,
435                int oldLeft, int oldTop, int oldRight, int oldBottom) {
436            if (mBottomBar.getVisibility() == View.VISIBLE) {
437                mBottomBarRect.set(left, top, right, bottom);
438            } else {
439                // If bottom bar is not visible, treat it as a 0x0 rect at the
440                // bottom right corner of the screen.
441                mBottomBarRect.set(right, bottom, right, bottom);
442            }
443
444            RectF previewArea = mTextureViewHelper.getPreviewArea();
445            // Use preview area and bottom bar rect to calculate the preview that is
446            // not covered by bottom bar.
447            if (mBottomBar.getResources().getConfiguration().orientation
448                    == Configuration.ORIENTATION_PORTRAIT) {
449                previewArea.bottom = Math.min(mBottomBarRect.top, previewArea.bottom);
450            } else {
451                previewArea.right = Math.min(mBottomBarRect.left, previewArea.right);
452            }
453
454            if (mUncoverPreviewAreaChangedListener != null) {
455                mUncoverPreviewAreaChangedListener.uncoveredPreviewAreaChanged(previewArea);
456            }
457        }
458    };
459    private PeekView mPeekView;
460
461    /**
462     * Provides current preview frame and the controls/overlay from the module that
463     * are shown on top of the preview.
464     */
465    public interface CameraModuleScreenShotProvider {
466        /**
467         * Returns the current preview frame down-sampled using the given down-sample
468         * factor.
469         *
470         * @param downSampleFactor the down sample factor for down sampling the
471         *                         preview frame. (e.g. a down sample factor of
472         *                         2 means to scale down the preview frame to 1/2
473         *                         the width and height.)
474         * @return down-sampled preview frame
475         */
476        public Bitmap getPreviewFrame(int downSampleFactor);
477
478        /**
479         * @return the controls and overlays that are currently showing on top of
480         *         the preview drawn into a bitmap with no scaling applied.
481         */
482        public Bitmap getPreviewOverlayAndControls();
483    }
484
485    /**
486     * Gets notified when the preview area that is not covered by bottom bar is
487     * changed.
488     */
489    public interface UncoveredPreviewAreaSizeChangedListener {
490        /**
491         * Gets called when the preview area that is not covered by bottom bar is
492         * changed.
493         *
494         * @param uncoveredPreviewArea the rect of the preview area that is not
495         *                             under bottom bar
496         */
497        public void uncoveredPreviewAreaChanged(RectF uncoveredPreviewArea);
498    }
499
500    private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider =
501            new CameraModuleScreenShotProvider() {
502                @Override
503                public Bitmap getPreviewFrame(int downSampleFactor) {
504                    if (mCameraRootView == null || mTextureView == null) {
505                        return null;
506                    }
507
508                    RectF previewArea = mTextureViewHelper.getPreviewArea();
509                    // Gets the bitmap from the preview TextureView.
510                    Bitmap preview = mTextureView.getBitmap(
511                            (int) previewArea.width() / downSampleFactor,
512                            (int) previewArea.height() / downSampleFactor);
513                    return preview;
514                }
515
516                @Override
517                public Bitmap getPreviewOverlayAndControls() {
518                    Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(),
519                            mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
520                    Canvas canvas = new Canvas(overlays);
521                    mCameraRootView.draw(canvas);
522                    return overlays;
523                }
524            };
525
526    private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
527
528    public long getCoverHiddenTime() {
529        return mCoverHiddenTime;
530    }
531
532    /**
533     * This resets the preview to have no applied transform matrix.
534     */
535    public void clearPreviewTransform() {
536        mTextureViewHelper.clearTransform();
537    }
538
539    public void updatePreviewAspectRatio(float aspectRatio) {
540        mTextureViewHelper.updateAspectRatio(aspectRatio);
541    }
542
543    /**
544     * This is to support modules that calculate their own transform matrix because
545     * they need to use a transform matrix to rotate the preview.
546     *
547     * @param matrix transform matrix to be set on the TextureView
548     */
549    public void updatePreviewTransform(Matrix matrix) {
550        mTextureViewHelper.updateTransform(matrix);
551    }
552
553    public interface AnimationFinishedListener {
554        public void onAnimationFinished(boolean success);
555    }
556
557    private class MyTouchListener implements View.OnTouchListener {
558        private boolean mScaleStarted = false;
559        @Override
560        public boolean onTouch(View v, MotionEvent event) {
561            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
562                mScaleStarted = false;
563            } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
564                mScaleStarted = true;
565            }
566            return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
567        }
568    }
569
570    /**
571     * This gesture listener finds out the direction of the scroll gestures and
572     * sends them to CameraAppUI to do further handling.
573     */
574    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
575        private MotionEvent mDown;
576
577        @Override
578        public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
579            if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
580                    || mSwipeState != IDLE
581                    || mIsCaptureIntent
582                    || !mSwipeEnabled) {
583                return false;
584            }
585
586            int deltaX = (int) (ev.getX() - mDown.getX());
587            int deltaY = (int) (ev.getY() - mDown.getY());
588            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
589                if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
590                    // Calculate the direction of the swipe.
591                    if (deltaX >= Math.abs(deltaY)) {
592                        // Swipe right.
593                        setSwipeState(SWIPE_RIGHT);
594                    } else if (deltaX <= -Math.abs(deltaY)) {
595                        // Swipe left.
596                        setSwipeState(SWIPE_LEFT);
597                    } else if (deltaY >= Math.abs(deltaX)) {
598                        // Swipe down.
599                        setSwipeState(SWIPE_DOWN);
600                    } else if (deltaY <= -Math.abs(deltaX)) {
601                        // Swipe up.
602                        setSwipeState(SWIPE_UP);
603                    }
604                }
605            }
606            return true;
607        }
608
609        private void setSwipeState(int swipeState) {
610            mSwipeState = swipeState;
611            // Notify new swipe detected.
612            onSwipeDetected(swipeState);
613        }
614
615        @Override
616        public boolean onDown(MotionEvent ev) {
617            mDown = MotionEvent.obtain(ev);
618            mSwipeState = IDLE;
619            return false;
620        }
621    }
622
623    public CameraAppUI(AppController controller, MainActivityLayout appRootView,
624            boolean isCaptureIntent) {
625        mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
626        mController = controller;
627        mIsCaptureIntent = isCaptureIntent;
628
629        mAppRootView = appRootView;
630        mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
631        mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
632        mModeTransitionView = (ModeTransitionView)
633                mAppRootView.findViewById(R.id.mode_transition_view);
634        mFilmstripBottomControls = new FilmstripBottomControls(controller,
635                (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_controls));
636        mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
637        mGestureDetector = new GestureDetector(controller.getAndroidContext(),
638                new MyGestureListener());
639        mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
640        if (mModeListView != null) {
641            mModeListView.setModeSwitchListener(this);
642            mModeListView.setModeListOpenListener(this);
643            mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider);
644        } else {
645            Log.e(TAG, "Cannot find mode list in the view hierarchy");
646        }
647        mUncoverPreviewAreaChangedListener =
648                mModeListView.getUncoveredPreviewAreaSizeChangedListener();
649        mAnimationManager = new AnimationManager();
650        mPeekView = (PeekView) appRootView.findViewById(R.id.peek_view);
651        initDisplayListener();
652    }
653
654    /**
655     * Enable or disable swipe gestures. We want to disable them e.g. while we
656     * record a video.
657     */
658    public void setSwipeEnabled(boolean enabled) {
659        mAppRootView.setSwipeEnabled(enabled);
660        mSwipeEnabled = enabled;
661    }
662
663    public void onDestroy() {
664        ((DisplayManager) mController.getAndroidContext()
665                .getSystemService(Context.DISPLAY_SERVICE))
666                .unregisterDisplayListener(mDisplayListener);
667    }
668
669    /**
670     * Initializes the display listener to listen to display changes such as
671     * 180-degree rotation change, which will not have an onConfigurationChanged
672     * callback.
673     */
674    private void initDisplayListener() {
675        if (ApiHelper.HAS_DISPLAY_LISTENER) {
676            mLastRotation = CameraUtil.getDisplayRotation(mController.getAndroidContext());
677
678            mDisplayListener = new DisplayManager.DisplayListener() {
679                @Override
680                public void onDisplayAdded(int arg0) {
681                    // Do nothing.
682                }
683
684                @Override
685                public void onDisplayChanged(int displayId) {
686                    int rotation = CameraUtil.getDisplayRotation(
687                            mController.getAndroidContext());
688                    if ((rotation - mLastRotation + 360) % 360 == 180
689                            && mPreviewStatusListener != null) {
690                        mPreviewStatusListener.onPreviewFlipped();
691                    }
692                    mLastRotation = rotation;
693                }
694
695                @Override
696                public void onDisplayRemoved(int arg0) {
697                    // Do nothing.
698                }
699            };
700
701            ((DisplayManager) mController.getAndroidContext()
702                    .getSystemService(Context.DISPLAY_SERVICE))
703                    .registerDisplayListener(mDisplayListener, null);
704        }
705    }
706
707    /**
708     * Redirects touch events to appropriate recipient views based on swipe direction.
709     * More specifically, swipe up and swipe down will be handled by the view that handles
710     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
711     * to mode list in order to bring up mode list.
712     */
713    private void onSwipeDetected(int swipeState) {
714        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
715            // Quick switch between modes.
716            int currentModuleIndex = mController.getCurrentModuleIndex();
717            final int moduleToTransitionTo =
718                    mController.getQuickSwitchToModuleId(currentModuleIndex);
719            if (currentModuleIndex != moduleToTransitionTo) {
720                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
721
722                int shadeColorId = R.color.mode_cover_default_color;
723                int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
724                        mController.getAndroidContext());
725
726                AnimationFinishedListener listener = new AnimationFinishedListener() {
727                    @Override
728                    public void onAnimationFinished(boolean success) {
729                        if (success) {
730                            mHideCoverRunnable = new Runnable() {
731                                @Override
732                                public void run() {
733                                    mModeTransitionView.startPeepHoleAnimation();
734                                }
735                            };
736                            mModeCoverState = COVER_SHOWN;
737                            // Go to new module when the previous operation is successful.
738                            mController.onModeSelected(moduleToTransitionTo);
739                        }
740                    }
741                };
742                if (mSwipeState == SWIPE_UP) {
743                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
744                } else {
745                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
746                }
747            }
748        } else if (swipeState == SWIPE_LEFT) {
749            // Pass the touch sequence to filmstrip layout.
750            UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.FILMSTRIP,
751                eventprotos.CameraEvent.InteractionCause.SWIPE_LEFT);
752            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
753        } else if (swipeState == SWIPE_RIGHT) {
754            // Pass the touch to mode switcher
755            mAppRootView.redirectTouchEventsTo(mModeListView);
756        }
757    }
758
759    /**
760     * Gets called when activity resumes in preview.
761     */
762    public void resume() {
763        if (mTextureView == null || mTextureView.getSurfaceTexture() != null) {
764            if (!mIsCaptureIntent) {
765                showShimmyDelayed();
766            }
767        } else {
768            // Show mode theme cover until preview is ready
769            showModeCoverUntilPreviewReady();
770        }
771        // Hide action bar first since we are in full screen mode first, and
772        // switch the system UI to lights-out mode.
773        mFilmstripPanel.hide();
774    }
775
776    /**
777     * A cover view showing the mode theme color and mode icon will be visible on
778     * top of preview until preview is ready (i.e. camera preview is started and
779     * the first frame has been received).
780     */
781    private void showModeCoverUntilPreviewReady() {
782        int modeId = mController.getCurrentModuleIndex();
783        int colorId = R.color.mode_cover_default_color;;
784        int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext());
785        mModeTransitionView.setupModeCover(colorId, iconId);
786        mHideCoverRunnable = new Runnable() {
787            @Override
788            public void run() {
789                mModeTransitionView.hideModeCover(new AnimationFinishedListener() {
790                    @Override
791                    public void onAnimationFinished(boolean success) {
792                        if (success) {
793                            showShimmyDelayed();
794                        }
795                    }
796                });
797            }
798        };
799        mModeCoverState = COVER_SHOWN;
800    }
801
802    private void showShimmyDelayed() {
803        if (!mIsCaptureIntent) {
804            // Show shimmy in SHIMMY_DELAY_MS
805            mShouldShowShimmy = mController.shouldShowShimmy();
806            if (mShouldShowShimmy) {
807                mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
808            }
809        }
810    }
811
812    private void hideModeCover() {
813        if (mHideCoverRunnable != null) {
814            mAppRootView.post(mHideCoverRunnable);
815            mHideCoverRunnable = null;
816        }
817        mModeCoverState = COVER_HIDDEN;
818        if (mCoverHiddenTime < 0) {
819            mCoverHiddenTime = System.currentTimeMillis();
820        }
821    }
822
823    @Override
824    public void onOpenFullScreen() {
825        if (mShouldShowShimmy) {
826            mController.decrementShimmyPlayTimes();
827            // Sets should show shimmy flag to false for this session (i.e. until
828            // next onResume)
829            mShouldShowShimmy = false;
830        }
831    }
832
833    @Override
834    public void onModeListOpenProgress(float progress) {
835        progress = 1 - progress;
836        float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
837        mModeOptionsToggle.setAlpha(interpolatedProgress);
838    }
839
840    @Override
841    public void onModeListClosed() {
842        // Make sure the alpha on mode options ellipse is reset when mode drawer
843        // is closed.
844        mModeOptionsToggle.setAlpha(1f);
845    }
846
847    /**
848     * Called when the back key is pressed.
849     *
850     * @return Whether the UI responded to the key event.
851     */
852    public boolean onBackPressed() {
853        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
854            return mFilmstripLayout.onBackPressed();
855        } else {
856            return mModeListView.onBackPressed();
857        }
858    }
859
860    /**
861     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
862     * listens to SurfaceTexture changes. In addition, listeners are set on
863     * dependent app ui elements.
864     *
865     * @param previewStatusListener the listener that gets notified when SurfaceTexture
866     *                              changes
867     */
868    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
869        mPreviewStatusListener = previewStatusListener;
870        if (mPreviewStatusListener != null) {
871            onPreviewListenerChanged();
872        }
873    }
874
875    /**
876     * When the PreviewStatusListener changes, listeners need to be
877     * set on the following app ui elements:
878     * {@link com.android.camera.ui.PreviewOverlay},
879     * {@link com.android.camera.ui.BottomBar},
880     * {@link com.android.camera.ui.IndicatorIconController}.
881     */
882    private void onPreviewListenerChanged() {
883        // Set a listener for recognizing preview gestures.
884        GestureDetector.OnGestureListener gestureListener
885            = mPreviewStatusListener.getGestureListener();
886        if (gestureListener != null) {
887            mPreviewOverlay.setGestureListener(gestureListener);
888        }
889        View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
890        if (touchListener != null) {
891            mPreviewOverlay.setTouchListener(touchListener);
892        }
893
894        mTextureViewHelper.setAutoAdjustTransform(
895            mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
896
897        if (mPreviewStatusListener.shouldAutoAdjustBottomBar()) {
898            mTextureViewHelper.addPreviewAreaSizeChangedListener(mBottomBar);
899
900            mBottomBar.setAdjustPreviewAreaListener(new BottomBar.AdjustPreviewAreaListener() {
901                @Override
902                public void centerPreviewAreaInRect(RectF rect) {
903                    mTextureViewHelper.centerPreviewInRect(rect);
904                }
905            });
906        }
907    }
908
909    /**
910     * This method should be called in onCameraOpened.  It defines CameraAppUI
911     * specific changes that depend on the camera or camera settings.
912     */
913    public void onChangeCamera() {
914        ModuleController moduleController = mController.getCurrentModuleController();
915        applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec());
916
917        if (mIndicatorIconController != null) {
918            // Sync the settings state with the indicator state.
919            mIndicatorIconController.syncIndicators();
920        }
921    }
922
923    /**
924     * Adds a listener to receive callbacks when preview area size changes.
925     */
926    public void addPreviewAreaSizeChangedListener(
927            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
928        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
929    }
930
931    /**
932     * Removes a listener that receives callbacks when preview area size changes.
933     */
934    public void removePreviewAreaSizeChangedListener(
935            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
936        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
937    }
938
939    /**
940     * This inflates generic_module layout, which contains all the shared views across
941     * modules. Then each module inflates their own views in the given view group. For
942     * now, this is called every time switching from a not-yet-refactored module to a
943     * refactored module. In the future, this should only need to be done once per app
944     * start.
945     */
946    public void prepareModuleUI() {
947        mCameraRootView.removeAllViews();
948        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
949                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
950        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
951
952        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
953        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
954        mTextureViewHelper = new TextureViewHelper(mTextureView);
955        mTextureViewHelper.setSurfaceTextureListener(this);
956        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
957
958        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
959        int unpressedColor = mController.getAndroidContext().getResources()
960            .getColor(R.color.bottombar_unpressed);
961        setBottomBarColor(unpressedColor);
962        int pressedColor = mController.getAndroidContext().getResources()
963            .getColor(R.color.bottombar_pressed);
964        setBottomBarPressedColor(pressedColor);
965
966        // Auto adjust the bottom bar for every module, so that panorama
967        // and photosphere can get a transparent bottom bar without any extra work.
968        // TODO: add and remove the bottom bar listener in onPreviewListenerChanged,
969        // based on whether the module needs to adjust the size of the bottom bar.
970        mTextureViewHelper.addPreviewAreaSizeChangedListener(mBottomBar);
971
972        mModeOptionsOverlay
973            = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
974        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeOptionsOverlay);
975
976        // Sets the visibility of the bottom bar and the mode options.
977        resetBottomControls(mController.getCurrentModuleController(),
978            mController.getCurrentModuleIndex());
979
980        mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
981        mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
982
983        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
984        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
985        mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
986
987        mCaptureOverlay = (CaptureAnimationOverlay)
988                mCameraRootView.findViewById(R.id.capture_overlay);
989        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
990        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
991        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
992
993        if (mIndicatorIconController == null) {
994            mIndicatorIconController =
995                new IndicatorIconController(mController, mAppRootView);
996        }
997        mIndicatorIconController.setListener(mModeOptionsOverlay);
998
999        mController.getButtonManager().load(mCameraRootView);
1000        mController.getButtonManager().setListener(mIndicatorIconController);
1001        mController.getSettingsManager().addListener(mIndicatorIconController);
1002
1003        mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1004        mBottomBar.addOnLayoutChangeListener(mBottomBarLayoutChangeListener);
1005        mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
1006    }
1007
1008    /**
1009     * Called indirectly from each module in their initialization to get a view group
1010     * to inflate the module specific views in.
1011     *
1012     * @return a view group for modules to attach views to
1013     */
1014    public FrameLayout getModuleRootView() {
1015        // TODO: Change it to mModuleUI when refactor is done
1016        return mCameraRootView;
1017    }
1018
1019    /**
1020     * Remove all the module specific views.
1021     */
1022    public void clearModuleUI() {
1023        if (mModuleUI != null) {
1024            mModuleUI.removeAllViews();
1025        }
1026        mTextureViewHelper.addPreviewAreaSizeChangedListener(null);
1027
1028        mPreviewStatusListener = null;
1029        mPreviewOverlay.reset();
1030        mFocusOverlay.setVisibility(View.INVISIBLE);
1031    }
1032
1033    /**
1034     * Gets called when preview is ready to start. It sets up one shot preview callback
1035     * in order to receive a callback when the preview frame is available, so that
1036     * the preview cover can be hidden to reveal preview.
1037     *
1038     * An alternative for getting the timing to hide preview cover is through
1039     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1040     * which is less accurate but therefore is the fallback for modules that manage
1041     * their own preview callbacks (as setting one preview callback will override
1042     * any other installed preview callbacks), or use camera2 API.
1043     */
1044    public void onPreviewReadyToStart() {
1045        if (mModeCoverState == COVER_SHOWN) {
1046            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1047            mController.setupOneShotPreviewListener();
1048        }
1049    }
1050
1051    /**
1052     * Gets called when preview is started.
1053     */
1054    public void onPreviewStarted() {
1055        if (mModeCoverState == COVER_SHOWN) {
1056            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1057        }
1058    }
1059
1060    /**
1061     * Gets notified when next preview frame comes in.
1062     */
1063    public void onNewPreviewFrame() {
1064        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1065        hideModeCover();
1066        mModeCoverState = COVER_HIDDEN;
1067    }
1068
1069    /**
1070     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1071     *
1072     * @param modeIndex mode index of the selected mode
1073     */
1074    @Override
1075    public void onModeSelected(int modeIndex) {
1076        mHideCoverRunnable = new Runnable() {
1077            @Override
1078            public void run() {
1079                mModeListView.startModeSelectionAnimation();
1080            }
1081        };
1082        mModeCoverState = COVER_SHOWN;
1083
1084        int lastIndex = mController.getCurrentModuleIndex();
1085        mController.onModeSelected(modeIndex);
1086        int currentIndex = mController.getCurrentModuleIndex();
1087
1088        if (lastIndex == currentIndex) {
1089            hideModeCover();
1090        }
1091    }
1092
1093    @Override
1094    public void onSettingsSelected() {
1095        mController.onSettingsSelected();
1096    }
1097
1098    @Override
1099    public int getCurrentModeIndex() {
1100        return mController.getCurrentModuleIndex();
1101    }
1102
1103    /********************** Capture animation **********************/
1104    /* TODO: This session is subject to UX changes. In addition to the generic
1105       flash animation and post capture animation, consider designating a parameter
1106       for specifying the type of animation, as well as an animation finished listener
1107       so that modules can have more knowledge of the status of the animation. */
1108
1109    /**
1110     * Starts the filmstrip peek animation.
1111     *
1112     * @param bitmap The bitmap to show.
1113     * @param strong Whether the animation shows more portion of the bitmap or
1114     *               not.
1115     */
1116    public void startPeekAnimation(Bitmap bitmap, boolean strong) {
1117        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1118            return;
1119        }
1120        mPeekView.startPeekAnimation(bitmap, strong);
1121    }
1122
1123    /**
1124     * Starts the pre-capture animation.
1125     */
1126    public void startPreCaptureAnimation() {
1127        mCaptureOverlay.startFlashAnimation();
1128    }
1129
1130    /**
1131     * Cancels the pre-capture animation.
1132     */
1133    public void cancelPreCaptureAnimation() {
1134        mAnimationManager.cancelAnimations();
1135    }
1136
1137    /**
1138     * Cancels the post-capture animation.
1139     */
1140    public void cancelPostCaptureAnimation() {
1141        mAnimationManager.cancelAnimations();
1142    }
1143
1144    public FilmstripContentPanel getFilmstripContentPanel() {
1145        return mFilmstripPanel;
1146    }
1147
1148    /**
1149     * @return The {@link com.android.camera.app.CameraAppUI.BottomControls} on the
1150     * bottom of the filmstrip.
1151     */
1152    public BottomControls getFilmstripBottomControls() {
1153        return mFilmstripBottomControls;
1154    }
1155
1156    /**
1157     * @param listener The listener for bottom controls.
1158     */
1159    public void setFilmstripBottomControlsListener(BottomControls.Listener listener) {
1160        mFilmstripBottomControls.setListener(listener);
1161    }
1162
1163    /***************************SurfaceTexture Api and Listener*********************************/
1164
1165    /**
1166     * Return the shared surface texture.
1167     */
1168    public SurfaceTexture getSurfaceTexture() {
1169        return mSurface;
1170    }
1171
1172    /**
1173     * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1174     */
1175    public int getSurfaceWidth() {
1176        return mSurfaceWidth;
1177    }
1178
1179    /**
1180     * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1181     */
1182    public int getSurfaceHeight() {
1183        return mSurfaceHeight;
1184    }
1185
1186    @Override
1187    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1188        mSurface = surface;
1189        mSurfaceWidth = width;
1190        mSurfaceHeight = height;
1191        Log.v(TAG, "SurfaceTexture is available");
1192        if (mPreviewStatusListener != null) {
1193            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1194        }
1195    }
1196
1197    @Override
1198    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1199        mSurface = surface;
1200        mSurfaceWidth = width;
1201        mSurfaceHeight = height;
1202        if (mPreviewStatusListener != null) {
1203            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1204        }
1205    }
1206
1207    @Override
1208    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1209        mSurface = null;
1210        Log.v(TAG, "SurfaceTexture is destroyed");
1211        if (mPreviewStatusListener != null) {
1212            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1213        }
1214        return false;
1215    }
1216
1217    @Override
1218    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1219        mSurface = surface;
1220        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1221            CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1222            hideModeCover();
1223            mModeCoverState = COVER_HIDDEN;
1224        }
1225        if (mPreviewStatusListener != null) {
1226            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1227        }
1228    }
1229
1230    /****************************Grid lines api ******************************/
1231
1232    /**
1233     * Show a set of evenly spaced lines over the preview.  The number
1234     * of lines horizontally and vertically is determined by
1235     * {@link com.android.camera.ui.GridLines}.
1236     */
1237    public void showGridLines() {
1238        if (mGridLines != null) {
1239            mGridLines.setVisibility(View.VISIBLE);
1240        }
1241    }
1242
1243    /**
1244     * Hide the set of evenly spaced grid lines overlaying the preview.
1245     */
1246    public void hideGridLines() {
1247        if (mGridLines != null) {
1248            mGridLines.setVisibility(View.INVISIBLE);
1249        }
1250    }
1251
1252    /**
1253     * Return a callback which shows or hide the preview grid lines
1254     * depending on whether the grid lines setting is set on.
1255     */
1256    public ButtonManager.ButtonCallback getGridLinesCallback() {
1257        return new ButtonManager.ButtonCallback() {
1258            @Override
1259            public void onStateChanged(int state) {
1260                if (mController.getSettingsManager().areGridLinesOn()) {
1261                    showGridLines();
1262                } else {
1263                    hideGridLines();
1264                }
1265            }
1266        };
1267    }
1268
1269    /***************************Mode options api *****************************/
1270
1271    /**
1272     * Set the mode options visible.
1273     */
1274    public void showModeOptions() {
1275        mModeOptionsOverlay.setVisibility(View.VISIBLE);
1276    }
1277
1278    /**
1279     * Set the mode options invisible.  This is necessary for modes
1280     * that don't show a bottom bar for the capture UI.
1281     */
1282    public void hideModeOptions() {
1283        mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1284    }
1285
1286    /****************************Bottom bar api ******************************/
1287
1288    /**
1289     * Sets up the bottom bar and mode options with the correct
1290     * shutter button and visibility based on the current module.
1291     */
1292    public void resetBottomControls(ModuleController module, int moduleIndex) {
1293        if (areBottomControlsUsed(module)) {
1294            setBottomBarShutterIcon(moduleIndex);
1295        }
1296    }
1297
1298    /**
1299     * Show or hide the mode options and bottom bar, based on
1300     * whether the current module is using the bottom bar.  Returns
1301     * whether the mode options and bottom bar are used.
1302     */
1303    private boolean areBottomControlsUsed(ModuleController module) {
1304        if (module.isUsingBottomBar()) {
1305            showBottomBar();
1306            showModeOptions();
1307            return true;
1308        } else {
1309            hideBottomBar();
1310            hideModeOptions();
1311            return false;
1312        }
1313    }
1314
1315    /**
1316     * Set the bottom bar visible.
1317     */
1318    public void showBottomBar() {
1319        mBottomBar.setVisibility(View.VISIBLE);
1320    }
1321
1322    /**
1323     * Set the bottom bar invisible.
1324     */
1325    public void hideBottomBar() {
1326        mBottomBar.setVisibility(View.INVISIBLE);
1327    }
1328
1329    /**
1330     * Sets the color of the bottom bar.
1331     */
1332    public void setBottomBarColor(int colorId) {
1333        mBottomBar.setBackgroundColor(colorId);
1334    }
1335
1336    /**
1337     * Sets the pressed color of the bottom bar.
1338     */
1339    public void setBottomBarPressedColor(int colorId) {
1340        mBottomBar.setBackgroundPressedColor(colorId);
1341    }
1342
1343    /**
1344     * Sets the shutter button icon on the bottom bar, based on
1345     * the mode index.
1346     */
1347    public void setBottomBarShutterIcon(int modeIndex) {
1348        int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1349            mController.getAndroidContext());
1350        mBottomBar.setShutterButtonIcon(shutterIconId);
1351    }
1352
1353    public void animateBottomBarToCircle(int shutterIconId) {
1354        mBottomBar.animateToCircle(shutterIconId);
1355    }
1356
1357    public void animateBottomBarToFullSize(int shutterIconId) {
1358        mBottomBar.animateToFullSize(shutterIconId);
1359    }
1360
1361    /**
1362     * Set the visibility of the bottom bar.
1363     */
1364    // TODO: needed for when panorama is managed by the generic module ui.
1365    public void setBottomBarVisible(boolean visible) {
1366        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1367    }
1368
1369    /**
1370     * If the bottom bar is visible (hence has been drawn),
1371     * this sets a {@link #ShutterButton.OnShutterButtonListener}
1372     * on the global shutter button,
1373     */
1374    public void setBottomBarShutterListener(
1375            ShutterButton.OnShutterButtonListener listener) {
1376        ShutterButton shutterButton
1377            = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1378        if (shutterButton != null) {
1379            shutterButton.setOnShutterButtonListener(listener);
1380        }
1381    }
1382
1383    /**
1384     * Performs a transition to the capture layout of the bottom bar.
1385     */
1386    public void transitionToCapture() {
1387        ModuleController moduleController = mController.getCurrentModuleController();
1388        applyModuleSpecs(moduleController.getHardwareSpec(),
1389            moduleController.getBottomBarSpec());
1390        mBottomBar.transitionToCapture();
1391    }
1392
1393    /**
1394     * Performs a transition to the global intent layout.
1395     */
1396    public void transitionToIntentCaptureLayout() {
1397        ModuleController moduleController = mController.getCurrentModuleController();
1398        applyModuleSpecs(moduleController.getHardwareSpec(),
1399            moduleController.getBottomBarSpec());
1400        mBottomBar.transitionToIntentCaptureLayout();
1401    }
1402
1403    /**
1404     * Performs a transition to the global intent review layout.
1405     */
1406    public void transitionToIntentReviewLayout() {
1407        ModuleController moduleController = mController.getCurrentModuleController();
1408        applyModuleSpecs(moduleController.getHardwareSpec(),
1409            moduleController.getBottomBarSpec());
1410        mBottomBar.transitionToIntentReviewLayout();
1411    }
1412
1413    /**
1414     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1415     * to the bottom bar mode options based on limitations from a
1416     * {@link com.android.camera.hardware.HardwareSpec}.
1417     *
1418     * Options not supported by the hardware are either hidden
1419     * or disabled, depending on the option.
1420     *
1421     * Otherwise, the option is fully enabled and clickable.
1422     */
1423    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1424           final BottomBarUISpec bottomBarSpec) {
1425        if (hardwareSpec == null || bottomBarSpec == null) {
1426            return;
1427        }
1428
1429        ButtonManager buttonManager = mController.getButtonManager();
1430        SettingsManager settingsManager = mController.getSettingsManager();
1431
1432        /** Standard mode options */
1433        if (hardwareSpec.isFrontCameraSupported()) {
1434            if (bottomBarSpec.enableCamera) {
1435                buttonManager.enableButton(ButtonManager.BUTTON_CAMERA,
1436                    bottomBarSpec.cameraCallback);
1437            } else {
1438                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1439            }
1440        } else {
1441            // Hide camera icon if front camera not available.
1442            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1443        }
1444
1445        boolean flashBackCamera = mController.getSettingsManager().getBoolean(
1446            SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA);
1447        if (bottomBarSpec.hideFlash || !flashBackCamera) {
1448            buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
1449        } else {
1450            if (hardwareSpec.isFlashSupported()) {
1451                if (bottomBarSpec.enableFlash) {
1452                    buttonManager.enableButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1453                } else if (bottomBarSpec.enableTorchFlash) {
1454                    buttonManager.enableButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1455                } else {
1456                    buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1457                }
1458            } else {
1459                // Disable flash icon if not supported by the hardware.
1460                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1461            }
1462        }
1463
1464        if (bottomBarSpec.hideHdr) {
1465            // Force hide hdr or hdr plus icon.
1466            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1467        } else {
1468            if (hardwareSpec.isHdrPlusSupported()) {
1469                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1470                    buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS,
1471                        bottomBarSpec.hdrCallback);
1472                } else {
1473                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1474                }
1475            } else if (hardwareSpec.isHdrSupported()) {
1476                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1477                    buttonManager.enableButton(ButtonManager.BUTTON_HDR,
1478                        bottomBarSpec.hdrCallback);
1479                } else {
1480                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1481                }
1482            } else {
1483                // Hide hdr plus or hdr icon if neither are supported.
1484                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1485            }
1486        }
1487
1488        if (bottomBarSpec.hideGridLines) {
1489            // Force hide grid lines icon.
1490            buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
1491            hideGridLines();
1492        } else {
1493            if (bottomBarSpec.enableGridLines) {
1494                buttonManager.enableButton(ButtonManager.BUTTON_GRID_LINES,
1495                    bottomBarSpec.gridLinesCallback != null ?
1496                    bottomBarSpec.gridLinesCallback : getGridLinesCallback());
1497            } else {
1498                buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
1499                hideGridLines();
1500            }
1501        }
1502
1503        if (bottomBarSpec.enablePanoOrientation
1504                && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
1505            buttonManager.enableButton(ButtonManager.BUTTON_PANO_ORIENTATION,
1506                    bottomBarSpec.panoOrientationCallback);
1507        } else {
1508            buttonManager.hideButton(ButtonManager.BUTTON_PANO_ORIENTATION);
1509        }
1510
1511        /** Intent UI */
1512        if (bottomBarSpec.showCancel) {
1513            buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL,
1514                bottomBarSpec.cancelCallback);
1515        }
1516        if (bottomBarSpec.showDone) {
1517            buttonManager.enablePushButton(ButtonManager.BUTTON_DONE,
1518                bottomBarSpec.doneCallback);
1519        }
1520        if (bottomBarSpec.showRetake) {
1521            buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE,
1522                bottomBarSpec.retakeCallback);
1523        }
1524    }
1525}
1526