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