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