CameraAppUI.java revision f495ea8ca078fc752b1ed1045ba22817d75e968e
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 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        mAppRootView.setSwipeEnabled(enabled);
702        mSwipeEnabled = enabled;
703    }
704
705    public void onDestroy() {
706        ((DisplayManager) mController.getAndroidContext()
707                .getSystemService(Context.DISPLAY_SERVICE))
708                .unregisterDisplayListener(mDisplayListener);
709    }
710
711    /**
712     * Initializes the display listener to listen to display changes such as
713     * 180-degree rotation change, which will not have an onConfigurationChanged
714     * callback.
715     */
716    private void initDisplayListener() {
717        if (ApiHelper.HAS_DISPLAY_LISTENER) {
718            mLastRotation = CameraUtil.getDisplayRotation(mController.getAndroidContext());
719
720            mDisplayListener = new DisplayManager.DisplayListener() {
721                @Override
722                public void onDisplayAdded(int arg0) {
723                    // Do nothing.
724                }
725
726                @Override
727                public void onDisplayChanged(int displayId) {
728                    int rotation = CameraUtil.getDisplayRotation(
729                            mController.getAndroidContext());
730                    if ((rotation - mLastRotation + 360) % 360 == 180
731                            && mPreviewStatusListener != null) {
732                        mPreviewStatusListener.onPreviewFlipped();
733                    }
734                    mLastRotation = rotation;
735                }
736
737                @Override
738                public void onDisplayRemoved(int arg0) {
739                    // Do nothing.
740                }
741            };
742
743            ((DisplayManager) mController.getAndroidContext()
744                    .getSystemService(Context.DISPLAY_SERVICE))
745                    .registerDisplayListener(mDisplayListener, null);
746        }
747    }
748
749    /**
750     * Redirects touch events to appropriate recipient views based on swipe direction.
751     * More specifically, swipe up and swipe down will be handled by the view that handles
752     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
753     * to mode list in order to bring up mode list.
754     */
755    private void onSwipeDetected(int swipeState) {
756        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
757            // TODO: Polish quick switch after this release.
758            // Quick switch between modes.
759            int currentModuleIndex = mController.getCurrentModuleIndex();
760            final int moduleToTransitionTo =
761                    mController.getQuickSwitchToModuleId(currentModuleIndex);
762            if (currentModuleIndex != moduleToTransitionTo) {
763                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
764
765                int shadeColorId = R.color.mode_cover_default_color;
766                int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
767                        mController.getAndroidContext());
768
769                AnimationFinishedListener listener = new AnimationFinishedListener() {
770                    @Override
771                    public void onAnimationFinished(boolean success) {
772                        if (success) {
773                            mHideCoverRunnable = new Runnable() {
774                                @Override
775                                public void run() {
776                                    mModeTransitionView.startPeepHoleAnimation();
777                                }
778                            };
779                            mModeCoverState = COVER_SHOWN;
780                            // Go to new module when the previous operation is successful.
781                            mController.onModeSelected(moduleToTransitionTo);
782                        }
783                    }
784                };
785                if (mSwipeState == SWIPE_UP) {
786                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
787                } else {
788                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
789                }
790            }
791        } else if (swipeState == SWIPE_LEFT) {
792            // Pass the touch sequence to filmstrip layout.
793            UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.FILMSTRIP,
794                eventprotos.CameraEvent.InteractionCause.SWIPE_LEFT);
795            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
796        } else if (swipeState == SWIPE_RIGHT) {
797            // Pass the touch to mode switcher
798            mAppRootView.redirectTouchEventsTo(mModeListView);
799        }
800    }
801
802    /**
803     * Gets called when activity resumes in preview.
804     */
805    public void resume() {
806        // Show mode theme cover until preview is ready
807        showModeCoverUntilPreviewReady();
808
809        // Hide action bar first since we are in full screen mode first, and
810        // switch the system UI to lights-out mode.
811        mFilmstripPanel.hide();
812    }
813
814    /**
815     * A cover view showing the mode theme color and mode icon will be visible on
816     * top of preview until preview is ready (i.e. camera preview is started and
817     * the first frame has been received).
818     */
819    private void showModeCoverUntilPreviewReady() {
820        int modeId = mController.getCurrentModuleIndex();
821        int colorId = R.color.mode_cover_default_color;;
822        int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext());
823        mModeTransitionView.setupModeCover(colorId, iconId);
824        mHideCoverRunnable = new Runnable() {
825            @Override
826            public void run() {
827                mModeTransitionView.hideModeCover(null);
828                showShimmyDelayed();
829            }
830        };
831        mModeCoverState = COVER_SHOWN;
832    }
833
834    private void showShimmyDelayed() {
835        if (!mIsCaptureIntent) {
836            // Show shimmy in SHIMMY_DELAY_MS
837            mModeListView.showModeSwitcherHint();
838        }
839    }
840
841    private void hideModeCover() {
842        if (mHideCoverRunnable != null) {
843            mAppRootView.post(mHideCoverRunnable);
844            mHideCoverRunnable = null;
845        }
846        mModeCoverState = COVER_HIDDEN;
847        if (mCoverHiddenTime < 0) {
848            mCoverHiddenTime = System.currentTimeMillis();
849        }
850    }
851
852    /**
853     * Call to stop the preview from being rendered.
854     */
855    public void pausePreviewRendering() {
856        mTextureView.setVisibility(View.INVISIBLE);
857    }
858
859    /**
860     * Call to begin rendering the preview again.
861     */
862    public void resumePreviewRendering() {
863        mTextureView.setVisibility(View.VISIBLE);
864    }
865
866    @Override
867    public void onOpenFullScreen() {
868        // Do nothing.
869    }
870
871    @Override
872    public void onModeListOpenProgress(float progress) {
873        progress = 1 - progress;
874        float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
875        mModeOptionsToggle.setAlpha(interpolatedProgress);
876        // Change shutter button alpha linearly based on the mode list open progress:
877        // set the alpha to disabled alpha when list is fully open, to enabled alpha
878        // when the list is fully closed.
879        mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED
880                + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED);
881    }
882
883    @Override
884    public void onModeListClosed() {
885        // Make sure the alpha on mode options ellipse is reset when mode drawer
886        // is closed.
887        mModeOptionsToggle.setAlpha(1f);
888        mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
889    }
890
891    /**
892     * Called when the back key is pressed.
893     *
894     * @return Whether the UI responded to the key event.
895     */
896    public boolean onBackPressed() {
897        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
898            return mFilmstripLayout.onBackPressed();
899        } else {
900            return mModeListView.onBackPressed();
901        }
902    }
903
904    /**
905     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
906     * listens to SurfaceTexture changes. In addition, listeners are set on
907     * dependent app ui elements.
908     *
909     * @param previewStatusListener the listener that gets notified when SurfaceTexture
910     *                              changes
911     */
912    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
913        mPreviewStatusListener = previewStatusListener;
914        if (mPreviewStatusListener != null) {
915            onPreviewListenerChanged();
916        }
917    }
918
919    /**
920     * When the PreviewStatusListener changes, listeners need to be
921     * set on the following app ui elements:
922     * {@link com.android.camera.ui.PreviewOverlay},
923     * {@link com.android.camera.ui.BottomBar},
924     * {@link com.android.camera.ui.IndicatorIconController}.
925     */
926    private void onPreviewListenerChanged() {
927        // Set a listener for recognizing preview gestures.
928        GestureDetector.OnGestureListener gestureListener
929            = mPreviewStatusListener.getGestureListener();
930        if (gestureListener != null) {
931            mPreviewOverlay.setGestureListener(gestureListener);
932        }
933        View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
934        if (touchListener != null) {
935            mPreviewOverlay.setTouchListener(touchListener);
936        }
937
938        mTextureViewHelper.setAutoAdjustTransform(
939                mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
940    }
941
942    /**
943     * This method should be called in onCameraOpened.  It defines CameraAppUI
944     * specific changes that depend on the camera or camera settings.
945     */
946    public void onChangeCamera() {
947        ModuleController moduleController = mController.getCurrentModuleController();
948        applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec());
949
950        if (mIndicatorIconController != null) {
951            // Sync the settings state with the indicator state.
952            mIndicatorIconController.syncIndicators();
953        }
954    }
955
956    /**
957     * Adds a listener to receive callbacks when preview area changes.
958     */
959    public void addPreviewAreaChangedListener(
960            PreviewStatusListener.PreviewAreaChangedListener listener) {
961        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
962    }
963
964    /**
965     * Removes a listener that receives callbacks when preview area changes.
966     */
967    public void removePreviewAreaChangedListener(
968            PreviewStatusListener.PreviewAreaChangedListener listener) {
969        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
970    }
971
972    /**
973     * This inflates generic_module layout, which contains all the shared views across
974     * modules. Then each module inflates their own views in the given view group. For
975     * now, this is called every time switching from a not-yet-refactored module to a
976     * refactored module. In the future, this should only need to be done once per app
977     * start.
978     */
979    public void prepareModuleUI() {
980        mCameraRootView.removeAllViews();
981        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
982                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
983        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
984
985        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
986        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
987        mTextureViewHelper = new TextureViewHelper(mTextureView);
988        mTextureViewHelper.setSurfaceTextureListener(this);
989        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
990
991        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
992        int unpressedColor = mController.getAndroidContext().getResources()
993            .getColor(R.color.bottombar_unpressed);
994        setBottomBarColor(unpressedColor);
995        int pressedColor = mController.getAndroidContext().getResources()
996            .getColor(R.color.bottombar_pressed);
997        setBottomBarPressedColor(pressedColor);
998
999        mModeOptionsOverlay
1000            = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
1001        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeOptionsOverlay);
1002
1003        // Sets the visibility of the bottom bar and the mode options.
1004        resetBottomControls(mController.getCurrentModuleController(),
1005            mController.getCurrentModuleIndex());
1006
1007        mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1008        addShutterListener(mController.getCurrentModuleController());
1009        addShutterListener(mModeOptionsOverlay);
1010
1011        mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
1012        mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
1013
1014        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
1015        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
1016        mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
1017
1018        mCaptureOverlay = (CaptureAnimationOverlay)
1019                mCameraRootView.findViewById(R.id.capture_overlay);
1020        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
1021        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
1022        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
1023
1024        if (mIndicatorIconController == null) {
1025            mIndicatorIconController =
1026                new IndicatorIconController(mController, mAppRootView);
1027        }
1028        mIndicatorIconController.setListener(mModeOptionsOverlay);
1029
1030        mController.getButtonManager().load(mCameraRootView);
1031        mController.getButtonManager().setListener(mIndicatorIconController);
1032        mController.getSettingsManager().addListener(mIndicatorIconController);
1033
1034        mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1035        mBottomBar.addOnLayoutChangeListener(mBottomBarLayoutChangeListener);
1036        mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
1037        mTutorialsPlaceholder = (FrameLayout) mCameraRootView
1038                .findViewById(R.id.tutorials_placeholder);
1039
1040        mTextureViewHelper.addPreviewAreaSizeChangedListener(
1041                new PreviewStatusListener.PreviewAreaChangedListener() {
1042                    @Override
1043                    public void onPreviewAreaChanged(RectF previewArea) {
1044                        if (mPreviewStatusListener != null &&
1045                                mPreviewStatusListener.shouldAutoAdjustBottomBar()) {
1046                            mBottomBar.onPreviewAreaChanged(previewArea);
1047                        } else {
1048                            mPeekView.setTranslationX(previewArea.right - mAppRootView.getRight());
1049                        }
1050                    }
1051                });
1052
1053        mBottomBar.setAdjustPreviewAreaListener(new BottomBar.AdjustPreviewAreaListener() {
1054            @Override
1055            public void fitAndCenterPreviewAreaInRect(RectF rect) {
1056                mPeekView.setTranslationX(0f);
1057                if (mPreviewStatusListener != null &&
1058                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1059                    mTextureViewHelper.centerPreviewInRect(rect);
1060                }
1061            }
1062
1063            @Override
1064            public void fitAndAlignBottomInRect(RectF rect) {
1065                mPeekView.setTranslationX(0f);
1066                if (mPreviewStatusListener != null &&
1067                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1068                    mTextureViewHelper.alignBottomInRect(rect);
1069                }
1070            }
1071
1072            @Override
1073            public void fitAndAlignRightInRect(RectF rect) {
1074                mPeekView.setTranslationX(rect.right - mAppRootView.getRight());
1075                if (mPreviewStatusListener != null &&
1076                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1077                    mTextureViewHelper.alignRightInRect(rect);
1078                }
1079            }
1080        });
1081    }
1082
1083    /**
1084     * Called indirectly from each module in their initialization to get a view group
1085     * to inflate the module specific views in.
1086     *
1087     * @return a view group for modules to attach views to
1088     */
1089    public FrameLayout getModuleRootView() {
1090        // TODO: Change it to mModuleUI when refactor is done
1091        return mCameraRootView;
1092    }
1093
1094    /**
1095     * Remove all the module specific views.
1096     */
1097    public void clearModuleUI() {
1098        if (mModuleUI != null) {
1099            mModuleUI.removeAllViews();
1100        }
1101        removeShutterListener(mController.getCurrentModuleController());
1102        mTutorialsPlaceholder.removeAllViews();
1103
1104        mPreviewStatusListener = null;
1105        mPreviewOverlay.reset();
1106        mFocusOverlay.setVisibility(View.INVISIBLE);
1107    }
1108
1109    /**
1110     * Gets called when preview is ready to start. It sets up one shot preview callback
1111     * in order to receive a callback when the preview frame is available, so that
1112     * the preview cover can be hidden to reveal preview.
1113     *
1114     * An alternative for getting the timing to hide preview cover is through
1115     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1116     * which is less accurate but therefore is the fallback for modules that manage
1117     * their own preview callbacks (as setting one preview callback will override
1118     * any other installed preview callbacks), or use camera2 API.
1119     */
1120    public void onPreviewReadyToStart() {
1121        if (mModeCoverState == COVER_SHOWN) {
1122            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1123            mController.setupOneShotPreviewListener();
1124        }
1125    }
1126
1127    /**
1128     * Gets called when preview is started.
1129     */
1130    public void onPreviewStarted() {
1131        if (mModeCoverState == COVER_SHOWN) {
1132            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1133        }
1134    }
1135
1136    /**
1137     * Gets notified when next preview frame comes in.
1138     */
1139    public void onNewPreviewFrame() {
1140        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1141        hideModeCover();
1142        mModeCoverState = COVER_HIDDEN;
1143    }
1144
1145    /**
1146     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1147     *
1148     * @param modeIndex mode index of the selected mode
1149     */
1150    @Override
1151    public void onModeSelected(int modeIndex) {
1152        mHideCoverRunnable = new Runnable() {
1153            @Override
1154            public void run() {
1155                mModeListView.startModeSelectionAnimation();
1156            }
1157        };
1158        mModeCoverState = COVER_SHOWN;
1159
1160        int lastIndex = mController.getCurrentModuleIndex();
1161        mController.onModeSelected(modeIndex);
1162        int currentIndex = mController.getCurrentModuleIndex();
1163
1164        if (lastIndex == currentIndex) {
1165            hideModeCover();
1166        }
1167    }
1168
1169    @Override
1170    public void onSettingsSelected() {
1171        mController.onSettingsSelected();
1172    }
1173
1174    @Override
1175    public int getCurrentModeIndex() {
1176        return mController.getCurrentModuleIndex();
1177    }
1178
1179    /********************** Capture animation **********************/
1180    /* TODO: This session is subject to UX changes. In addition to the generic
1181       flash animation and post capture animation, consider designating a parameter
1182       for specifying the type of animation, as well as an animation finished listener
1183       so that modules can have more knowledge of the status of the animation. */
1184
1185    /**
1186     * Starts the filmstrip peek animation.
1187     *
1188     * @param bitmap The bitmap to show.
1189     * @param strong Whether the animation shows more portion of the bitmap or
1190     *               not.
1191     */
1192    public void startPeekAnimation(Bitmap bitmap, boolean strong) {
1193        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1194            return;
1195        }
1196        mPeekView.startPeekAnimation(bitmap, strong);
1197    }
1198
1199    /**
1200     * Starts the pre-capture animation.
1201     */
1202    public void startPreCaptureAnimation() {
1203        mCaptureOverlay.startFlashAnimation();
1204    }
1205
1206    /**
1207     * Cancels the pre-capture animation.
1208     */
1209    public void cancelPreCaptureAnimation() {
1210        mAnimationManager.cancelAnimations();
1211    }
1212
1213    /**
1214     * Cancels the post-capture animation.
1215     */
1216    public void cancelPostCaptureAnimation() {
1217        mAnimationManager.cancelAnimations();
1218    }
1219
1220    public FilmstripContentPanel getFilmstripContentPanel() {
1221        return mFilmstripPanel;
1222    }
1223
1224    /**
1225     * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the
1226     * bottom of the filmstrip.
1227     */
1228    public BottomPanel getFilmstripBottomControls() {
1229        return mFilmstripBottomControls;
1230    }
1231
1232    /**
1233     * @param listener The listener for bottom controls.
1234     */
1235    public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) {
1236        mFilmstripBottomControls.setListener(listener);
1237    }
1238
1239    /***************************SurfaceTexture Api and Listener*********************************/
1240
1241    /**
1242     * Return the shared surface texture.
1243     */
1244    public SurfaceTexture getSurfaceTexture() {
1245        return mSurface;
1246    }
1247
1248    /**
1249     * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1250     */
1251    public int getSurfaceWidth() {
1252        return mSurfaceWidth;
1253    }
1254
1255    /**
1256     * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1257     */
1258    public int getSurfaceHeight() {
1259        return mSurfaceHeight;
1260    }
1261
1262    @Override
1263    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1264        mSurface = surface;
1265        mSurfaceWidth = width;
1266        mSurfaceHeight = height;
1267        Log.v(TAG, "SurfaceTexture is available");
1268        if (mPreviewStatusListener != null) {
1269            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1270        }
1271    }
1272
1273    @Override
1274    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1275        mSurface = surface;
1276        mSurfaceWidth = width;
1277        mSurfaceHeight = height;
1278        if (mPreviewStatusListener != null) {
1279            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1280        }
1281    }
1282
1283    @Override
1284    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1285        mSurface = null;
1286        Log.v(TAG, "SurfaceTexture is destroyed");
1287        if (mPreviewStatusListener != null) {
1288            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1289        }
1290        return false;
1291    }
1292
1293    @Override
1294    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1295        mSurface = surface;
1296        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1297            CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1298            hideModeCover();
1299            mModeCoverState = COVER_HIDDEN;
1300        }
1301        if (mPreviewStatusListener != null) {
1302            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1303        }
1304    }
1305
1306    /****************************Grid lines api ******************************/
1307
1308    /**
1309     * Show a set of evenly spaced lines over the preview.  The number
1310     * of lines horizontally and vertically is determined by
1311     * {@link com.android.camera.ui.GridLines}.
1312     */
1313    public void showGridLines() {
1314        if (mGridLines != null) {
1315            mGridLines.setVisibility(View.VISIBLE);
1316        }
1317    }
1318
1319    /**
1320     * Hide the set of evenly spaced grid lines overlaying the preview.
1321     */
1322    public void hideGridLines() {
1323        if (mGridLines != null) {
1324            mGridLines.setVisibility(View.INVISIBLE);
1325        }
1326    }
1327
1328    /**
1329     * Return a callback which shows or hide the preview grid lines
1330     * depending on whether the grid lines setting is set on.
1331     */
1332    public ButtonManager.ButtonCallback getGridLinesCallback() {
1333        return new ButtonManager.ButtonCallback() {
1334            @Override
1335            public void onStateChanged(int state) {
1336                if (mController.getSettingsManager().areGridLinesOn()) {
1337                    showGridLines();
1338                } else {
1339                    hideGridLines();
1340                }
1341            }
1342        };
1343    }
1344
1345    /***************************Mode options api *****************************/
1346
1347    /**
1348     * Set the mode options visible.
1349     */
1350    public void showModeOptions() {
1351        mModeOptionsOverlay.setVisibility(View.VISIBLE);
1352    }
1353
1354    /**
1355     * Set the mode options invisible.  This is necessary for modes
1356     * that don't show a bottom bar for the capture UI.
1357     */
1358    public void hideModeOptions() {
1359        mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1360    }
1361
1362    /****************************Bottom bar api ******************************/
1363
1364    /**
1365     * Sets up the bottom bar and mode options with the correct
1366     * shutter button and visibility based on the current module.
1367     */
1368    public void resetBottomControls(ModuleController module, int moduleIndex) {
1369        if (areBottomControlsUsed(module)) {
1370            setBottomBarShutterIcon(moduleIndex);
1371        }
1372    }
1373
1374    /**
1375     * Show or hide the mode options and bottom bar, based on
1376     * whether the current module is using the bottom bar.  Returns
1377     * whether the mode options and bottom bar are used.
1378     */
1379    private boolean areBottomControlsUsed(ModuleController module) {
1380        if (module.isUsingBottomBar()) {
1381            showBottomBar();
1382            showModeOptions();
1383            return true;
1384        } else {
1385            hideBottomBar();
1386            hideModeOptions();
1387            return false;
1388        }
1389    }
1390
1391    /**
1392     * Set the bottom bar visible.
1393     */
1394    public void showBottomBar() {
1395        mBottomBar.setVisibility(View.VISIBLE);
1396    }
1397
1398    /**
1399     * Set the bottom bar invisible.
1400     */
1401    public void hideBottomBar() {
1402        mBottomBar.setVisibility(View.INVISIBLE);
1403    }
1404
1405    /**
1406     * Sets the color of the bottom bar.
1407     */
1408    public void setBottomBarColor(int colorId) {
1409        mBottomBar.setBackgroundColor(colorId);
1410    }
1411
1412    /**
1413     * Sets the pressed color of the bottom bar.
1414     */
1415    public void setBottomBarPressedColor(int colorId) {
1416        mBottomBar.setBackgroundPressedColor(colorId);
1417    }
1418
1419    /**
1420     * Sets the shutter button icon on the bottom bar, based on
1421     * the mode index.
1422     */
1423    public void setBottomBarShutterIcon(int modeIndex) {
1424        int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1425            mController.getAndroidContext());
1426        mBottomBar.setShutterButtonIcon(shutterIconId);
1427    }
1428
1429    public void animateBottomBarToVideoStop(int shutterIconId) {
1430        mBottomBar.animateToVideoStop(shutterIconId);
1431    }
1432
1433    public void animateBottomBarToFullSize(int shutterIconId) {
1434        mBottomBar.animateToFullSize(shutterIconId);
1435    }
1436
1437    public void setCaptureButtonEnabled(boolean enabled) {
1438        mBottomBar.setCaptureButtonEnabled(enabled);
1439    }
1440
1441    public void setModeOptionsVisible(boolean visible) {
1442        mModeOptionsOverlay.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1443    }
1444
1445    /**
1446     * Set the visibility of the bottom bar.
1447     */
1448    // TODO: needed for when panorama is managed by the generic module ui.
1449    public void setBottomBarVisible(boolean visible) {
1450        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1451    }
1452
1453    /**
1454     * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button.
1455     */
1456    public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1457        mShutterButton.addOnShutterButtonListener(listener);
1458    }
1459
1460    /**
1461     * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
1462     */
1463    public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) {
1464        mShutterButton.removeOnShutterButtonListener(listener);
1465    }
1466
1467    /**
1468     * Performs a transition to the capture layout of the bottom bar.
1469     */
1470    public void transitionToCapture() {
1471        ModuleController moduleController = mController.getCurrentModuleController();
1472        applyModuleSpecs(moduleController.getHardwareSpec(),
1473            moduleController.getBottomBarSpec());
1474        mBottomBar.transitionToCapture();
1475    }
1476
1477    /**
1478     * Displays the Cancel button instead of the capture button.
1479     */
1480    public void transitionToCancel() {
1481        ModuleController moduleController = mController.getCurrentModuleController();
1482        applyModuleSpecs(moduleController.getHardwareSpec(),
1483                moduleController.getBottomBarSpec());
1484        mBottomBar.transitionToCancel();
1485    }
1486
1487    /**
1488     * Performs a transition to the global intent layout.
1489     */
1490    public void transitionToIntentCaptureLayout() {
1491        ModuleController moduleController = mController.getCurrentModuleController();
1492        applyModuleSpecs(moduleController.getHardwareSpec(),
1493            moduleController.getBottomBarSpec());
1494        mBottomBar.transitionToIntentCaptureLayout();
1495    }
1496
1497    /**
1498     * Performs a transition to the global intent review layout.
1499     */
1500    public void transitionToIntentReviewLayout() {
1501        ModuleController moduleController = mController.getCurrentModuleController();
1502        applyModuleSpecs(moduleController.getHardwareSpec(),
1503            moduleController.getBottomBarSpec());
1504        mBottomBar.transitionToIntentReviewLayout();
1505    }
1506
1507    /**
1508     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1509     * to the bottom bar mode options based on limitations from a
1510     * {@link com.android.camera.hardware.HardwareSpec}.
1511     *
1512     * Options not supported by the hardware are either hidden
1513     * or disabled, depending on the option.
1514     *
1515     * Otherwise, the option is fully enabled and clickable.
1516     */
1517    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1518           final BottomBarUISpec bottomBarSpec) {
1519        if (hardwareSpec == null || bottomBarSpec == null) {
1520            return;
1521        }
1522
1523        ButtonManager buttonManager = mController.getButtonManager();
1524        SettingsManager settingsManager = mController.getSettingsManager();
1525
1526        /** Standard mode options */
1527        if (hardwareSpec.isFrontCameraSupported()) {
1528            if (bottomBarSpec.enableCamera) {
1529                buttonManager.enableButton(ButtonManager.BUTTON_CAMERA,
1530                    bottomBarSpec.cameraCallback);
1531            } else {
1532                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1533            }
1534        } else {
1535            // Hide camera icon if front camera not available.
1536            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1537        }
1538
1539        boolean flashBackCamera = mController.getSettingsManager().getBoolean(
1540            SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA);
1541        if (bottomBarSpec.hideFlash || !flashBackCamera) {
1542            buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
1543        } else {
1544            if (hardwareSpec.isFlashSupported()) {
1545                if (bottomBarSpec.enableFlash) {
1546                    buttonManager.enableButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1547                } else if (bottomBarSpec.enableTorchFlash) {
1548                    buttonManager.enableButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1549                } else {
1550                    buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1551                }
1552            } else {
1553                // Disable flash icon if not supported by the hardware.
1554                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1555            }
1556        }
1557
1558        if (bottomBarSpec.hideHdr || mIsCaptureIntent) {
1559            // Force hide hdr or hdr plus icon.
1560            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1561        } else {
1562            if (hardwareSpec.isHdrPlusSupported()) {
1563                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1564                    buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS,
1565                        bottomBarSpec.hdrCallback);
1566                } else {
1567                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1568                }
1569            } else if (hardwareSpec.isHdrSupported()) {
1570                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1571                    buttonManager.enableButton(ButtonManager.BUTTON_HDR,
1572                        bottomBarSpec.hdrCallback);
1573                } else {
1574                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1575                }
1576            } else {
1577                // Hide hdr plus or hdr icon if neither are supported.
1578                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1579            }
1580        }
1581
1582        if (bottomBarSpec.hideGridLines) {
1583            // Force hide grid lines icon.
1584            buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
1585            hideGridLines();
1586        } else {
1587            if (bottomBarSpec.enableGridLines) {
1588                buttonManager.enableButton(ButtonManager.BUTTON_GRID_LINES,
1589                    bottomBarSpec.gridLinesCallback != null ?
1590                    bottomBarSpec.gridLinesCallback : getGridLinesCallback());
1591            } else {
1592                buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
1593                hideGridLines();
1594            }
1595        }
1596
1597        if (bottomBarSpec.enablePanoOrientation
1598                && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
1599            buttonManager.enableButton(ButtonManager.BUTTON_PANO_ORIENTATION,
1600                    bottomBarSpec.panoOrientationCallback);
1601        } else {
1602            buttonManager.hideButton(ButtonManager.BUTTON_PANO_ORIENTATION);
1603        }
1604
1605        /** Intent UI */
1606        if (bottomBarSpec.showCancel) {
1607            buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL,
1608                bottomBarSpec.cancelCallback);
1609        }
1610        if (bottomBarSpec.showDone) {
1611            buttonManager.enablePushButton(ButtonManager.BUTTON_DONE,
1612                bottomBarSpec.doneCallback);
1613        }
1614        if (bottomBarSpec.showRetake) {
1615            buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE,
1616                bottomBarSpec.retakeCallback);
1617        }
1618        if (bottomBarSpec.showReview) {
1619            buttonManager.enablePushButton(ButtonManager.BUTTON_REVIEW,
1620               bottomBarSpec.reviewCallback,
1621               R.drawable.ic_play);
1622        }
1623    }
1624
1625    /**
1626     * Shows the given tutorial on the screen.
1627     */
1628    public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) {
1629        tutorial.show(mTutorialsPlaceholder, inflater);
1630    }
1631
1632    /***************************Filmstrip api *****************************/
1633
1634    public void showFilmstrip() {
1635        mFilmstripLayout.showFilmstrip();
1636    }
1637
1638    public void hideFilmstrip() {
1639        mFilmstripLayout.hideFilmstrip();
1640    }
1641}
1642