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