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