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