CameraAppUI.java revision b259701e4f421b10404425d2a827cbe9d2571585
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.app;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Matrix;
24import android.graphics.RectF;
25import android.graphics.SurfaceTexture;
26import android.hardware.display.DisplayManager;
27import android.util.CameraPerformanceTracker;
28import android.util.Log;
29import android.view.GestureDetector;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.TextureView;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewGroup;
36import android.widget.FrameLayout;
37
38import com.android.camera.AnimationManager;
39import com.android.camera.ButtonManager;
40import com.android.camera.ShutterButton;
41import com.android.camera.TextureViewHelper;
42import com.android.camera.filmstrip.FilmstripContentPanel;
43import com.android.camera.hardware.HardwareSpec;
44import com.android.camera.module.ModuleController;
45import com.android.camera.settings.SettingsManager;
46import com.android.camera.ui.BottomBar;
47import com.android.camera.ui.CaptureAnimationOverlay;
48import com.android.camera.ui.GridLines;
49import com.android.camera.ui.MainActivityLayout;
50import com.android.camera.ui.ModeListView;
51import com.android.camera.ui.ModeTransitionView;
52import com.android.camera.ui.PreviewOverlay;
53import com.android.camera.ui.PreviewStatusListener;
54import com.android.camera.util.ApiHelper;
55import com.android.camera.util.CameraUtil;
56import com.android.camera.util.Gusterpolator;
57import com.android.camera.util.PhotoSphereHelper;
58import com.android.camera.util.UsageStatistics;
59import com.android.camera.widget.FilmstripLayout;
60import com.android.camera.widget.IndicatorIconController;
61import com.android.camera.widget.ModeOptionsOverlay;
62import com.android.camera.widget.PeekView;
63import com.android.camera2.R;
64import com.google.common.logging.eventprotos;
65
66/**
67 * CameraAppUI centralizes control of views shared across modules. Whereas module
68 * specific views will be handled in each Module UI. For example, we can now
69 * bring the flash animation and capture animation up from each module to app
70 * level, as these animations are largely the same for all modules.
71 *
72 * This class also serves to disambiguate touch events. It recognizes all the
73 * swipe gestures that happen on the preview by attaching a touch listener to
74 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
75 * of how swipe from each direction should be handled, it can then redirect these
76 * events to appropriate recipient views.
77 */
78public class CameraAppUI implements ModeListView.ModeSwitchListener,
79        TextureView.SurfaceTextureListener, ModeListView.ModeListOpenListener {
80
81    /**
82     * The bottom controls on the filmstrip.
83     */
84    public static interface BottomPanel {
85        /** Values for the view state of the button. */
86        public final int VIEWER_NONE = 0;
87        public final int VIEWER_PHOTO_SPHERE = 1;
88        public final int VIEWER_REFOCUS = 2;
89        public final int VIEWER_OTHER = 3;
90
91        /**
92         * Sets a new or replaces an existing listener for bottom control events.
93         */
94        void setListener(Listener listener);
95
96        /**
97         * Set if the bottom controls are visible.
98         * @param visible {@code true} if visible.
99         */
100        void setVisible(boolean visible);
101
102        /**
103         * @param visible Whether the button is visible.
104         */
105        void setEditButtonVisibility(boolean visible);
106
107        /**
108         * @param enabled Whether the button is enabled.
109         */
110        void setEditEnabled(boolean enabled);
111
112        /**
113         * Sets the visibility of the view-photosphere button.
114         *
115         * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE},
116         *            {@link #VIEWER_REFOCUS}.
117         */
118        void setViewerButtonVisibility(int state);
119
120        /**
121         * @param enabled Whether the button is enabled.
122         */
123        void setViewEnabled(boolean enabled);
124
125        /**
126         * @param enabled Whether the button is enabled.
127         */
128        void setTinyPlanetEnabled(boolean enabled);
129
130        /**
131         * @param visible Whether the button is visible.
132         */
133        void setDeleteButtonVisibility(boolean visible);
134
135        /**
136         * @param enabled Whether the button is enabled.
137         */
138        void setDeleteEnabled(boolean enabled);
139
140        /**
141         * @param visible Whether the button is visible.
142         */
143        void setShareButtonVisibility(boolean visible);
144
145        /**
146         * @param enabled Whether the button is enabled.
147         */
148        void setShareEnabled(boolean enabled);
149
150        /**
151         * Sets the texts for progress UI.
152         *
153         * @param text The text to show.
154         */
155        void setProgressText(CharSequence text);
156
157        /**
158         * Sets the progress.
159         *
160         * @param progress The progress value. Should be between 0 and 100.
161         */
162        void setProgress(int progress);
163
164        /**
165         * Replaces the progress UI with an error message.
166         */
167        void showProgressError(CharSequence message);
168
169        /**
170         * Hide the progress error message.
171         */
172        void hideProgressError();
173
174        /**
175         * Shows the progress.
176         */
177        void showProgress();
178
179        /**
180         * Hides the progress.
181         */
182        void hideProgress();
183
184        /**
185         * Shows the controls.
186         */
187        void showControls();
188
189        /**
190         * Hides the controls.
191         */
192        void hideControls();
193
194        /**
195         * Classes implementing this interface can listen for events on the bottom
196         * controls.
197         */
198        public static interface Listener {
199            /**
200             * Called when the user pressed the "view" button to e.g. view a photo
201             * sphere or RGBZ image.
202             */
203            public void onExternalViewer();
204
205            /**
206             * Called when the "edit" button is pressed.
207             */
208            public void onEdit();
209
210            /**
211             * Called when the "tiny planet" button is pressed.
212             */
213            public void onTinyPlanet();
214
215            /**
216             * Called when the "delete" button is pressed.
217             */
218            public void onDelete();
219
220            /**
221             * Called when the "share" button is pressed.
222             */
223            public void onShare();
224
225            /**
226             * Called when the progress error message is clicked.
227             */
228            public void onProgressErrorClicked();
229        }
230    }
231
232    /**
233     * BottomBarUISpec provides a structure for modules
234     * to specify their ideal bottom bar mode options layout.
235     *
236     * Once constructed by a module, this class should be
237     * treated as read only.
238     *
239     * The application then edits this spec according to
240     * hardware limitations and displays the final bottom
241     * bar ui.
242     */
243    public static class BottomBarUISpec {
244        /** Mode options UI */
245
246        /**
247         * Set true if the camera option should be enabled.
248         * If not set or false, and multiple cameras are supported,
249         * the camera option will be disabled.
250         *
251         * If multiple cameras are not supported, this preference
252         * is ignored and the camera option will not be visible.
253         */
254        public boolean enableCamera;
255
256        /**
257         * Set true if the camera option should not be visible, regardless
258         * of hardware limitations.
259         */
260        public boolean hideCamera;
261
262        /**
263         * Set true if the photo flash option should be enabled.
264         * If not set or false, the photo flash option will be
265         * disabled.
266         *
267         * If the hardware does not support multiple flash values,
268         * this preference is ignored and the flash option will
269         * be disabled.  It will not be made invisible in order to
270         * preserve a consistent experience across devices and between
271         * front and back cameras.
272         */
273        public boolean enableFlash;
274
275        /**
276         * Set true if the video flash option should be enabled.
277         * Same disable rules apply as the photo flash option.
278         */
279        public boolean enableTorchFlash;
280
281        /**
282         * Set true if flash should not be visible, regardless of
283         * hardware limitations.
284         */
285        public boolean hideFlash;
286
287        /**
288         * Set true if the hdr/hdr+ option should be enabled.
289         * If not set or false, the hdr/hdr+ option will be disabled.
290         *
291         * Hdr or hdr+ will be chosen based on hardware limitations,
292         * with hdr+ prefered.
293         *
294         * If hardware supports neither hdr nor hdr+, then the hdr/hdr+
295         * will not be visible.
296         */
297        public boolean enableHdr;
298
299        /**
300         * Set true if hdr/hdr+ should not be visible, regardless of
301         * hardware limitations.
302         */
303        public boolean hideHdr;
304
305        /**
306         * Set true if grid lines should be visible.  Not setting this
307         * causes grid lines to be disabled.  This option is agnostic to
308         * the hardware.
309         */
310        public boolean enableGridLines;
311
312        /**
313         * Set true if grid lines should not be visible.
314         */
315        public boolean hideGridLines;
316
317        /**
318         * Set true if the panorama orientation option should be visible.
319         *
320         * This option is not constrained by hardware limitations.
321         */
322        public boolean enablePanoOrientation;
323
324        /** Intent UI */
325
326        /**
327         * Set true if the intent ui cancel option should be visible.
328         */
329        public boolean showCancel;
330        /**
331         * Set true if the intent ui done option should be visible.
332         */
333        public boolean showDone;
334        /**
335         * Set true if the intent ui retake option should be visible.
336         */
337        public boolean showRetake;
338        /**
339         * Set true if the intent ui review option should be visible.
340         */
341        public boolean showReview;
342
343        /** Mode options callbacks */
344
345        /**
346         * A {@link com.android.camera.ButtonManager.ButtonCallback}
347         * that will be executed when the camera option is pressed. This
348         * callback can be null.
349         */
350        public ButtonManager.ButtonCallback cameraCallback;
351
352        /**
353         * A {@link com.android.camera.ButtonManager.ButtonCallback}
354         * that will be executed when the flash option is pressed. This
355         * callback can be null.
356         */
357        public ButtonManager.ButtonCallback flashCallback;
358
359        /**
360         * A {@link com.android.camera.ButtonManager.ButtonCallback}
361         * that will be executed when the hdr/hdr+ option is pressed. This
362         * callback can be null.
363         */
364        public ButtonManager.ButtonCallback hdrCallback;
365
366        /**
367         * A {@link com.android.camera.ButtonManager.ButtonCallback}
368         * that will be executed when the grid lines option is pressed. This
369         * callback can be null.
370         */
371        public ButtonManager.ButtonCallback gridLinesCallback;
372
373        /**
374         * A {@link com.android.camera.ButtonManager.ButtonCallback}
375         * that will execute when the panorama orientation option is pressed.
376         * This callback can be null.
377         */
378        public ButtonManager.ButtonCallback panoOrientationCallback;
379
380        /** Intent UI callbacks */
381
382        /**
383         * A {@link android.view.View.OnClickListener} that will execute
384         * when the cancel option is pressed. This callback can be null.
385         */
386        public View.OnClickListener cancelCallback;
387
388        /**
389         * A {@link android.view.View.OnClickListener} that will execute
390         * when the done option is pressed. This callback can be null.
391         */
392        public View.OnClickListener doneCallback;
393
394        /**
395         * A {@link android.view.View.OnClickListener} that will execute
396         * when the retake option is pressed. This callback can be null.
397         */
398        public View.OnClickListener retakeCallback;
399
400        /**
401         * A {@link android.view.View.OnClickListener} that will execute
402         * when the review option is pressed. This callback can be null.
403         */
404        public View.OnClickListener reviewCallback;
405    }
406
407
408    private final static String TAG = "CameraAppUI";
409
410    private final AppController mController;
411    private final boolean mIsCaptureIntent;
412    private final AnimationManager mAnimationManager;
413
414    // Swipe states:
415    private final static int IDLE = 0;
416    private final static int SWIPE_UP = 1;
417    private final static int SWIPE_DOWN = 2;
418    private final static int SWIPE_LEFT = 3;
419    private final static int SWIPE_RIGHT = 4;
420    private boolean mSwipeEnabled = true;
421
422    // Shared Surface Texture properities.
423    private SurfaceTexture mSurface;
424    private int mSurfaceWidth;
425    private int mSurfaceHeight;
426
427    // Touch related measures:
428    private final int mSlop;
429    private final static int SWIPE_TIME_OUT_MS = 500;
430
431    // Mode cover states:
432    private final static int COVER_HIDDEN = 0;
433    private final static int COVER_SHOWN = 1;
434    private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
435    private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3;
436
437    // App level views:
438    private final FrameLayout mCameraRootView;
439    private final ModeTransitionView mModeTransitionView;
440    private final MainActivityLayout mAppRootView;
441    private final ModeListView mModeListView;
442    private final FilmstripLayout mFilmstripLayout;
443    private TextureView mTextureView;
444    private FrameLayout mModuleUI;
445    private ShutterButton mShutterButton;
446    private BottomBar mBottomBar;
447    private ModeOptionsOverlay mModeOptionsOverlay;
448    private boolean mShouldShowShimmy = false;
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            mShouldShowShimmy = mController.shouldShowShimmy();
838            if (mShouldShowShimmy) {
839                mModeListView.showModeSwitcherHint();
840            }
841        }
842    }
843
844    private void hideModeCover() {
845        if (mHideCoverRunnable != null) {
846            mAppRootView.post(mHideCoverRunnable);
847            mHideCoverRunnable = null;
848        }
849        mModeCoverState = COVER_HIDDEN;
850        if (mCoverHiddenTime < 0) {
851            mCoverHiddenTime = System.currentTimeMillis();
852        }
853    }
854
855    @Override
856    public void onOpenFullScreen() {
857        if (mShouldShowShimmy) {
858            mController.decrementShimmyPlayTimes();
859            // Sets should show shimmy flag to false for this session (i.e. until
860            // next onResume)
861            mShouldShowShimmy = false;
862        }
863    }
864
865    @Override
866    public void onModeListOpenProgress(float progress) {
867        progress = 1 - progress;
868        float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
869        mModeOptionsToggle.setAlpha(interpolatedProgress);
870        // Change shutter button alpha linearly based on the mode list open progress:
871        // set the alpha to disabled alpha when list is fully open, to enabled alpha
872        // when the list is fully closed.
873        mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED
874                + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED);
875    }
876
877    @Override
878    public void onModeListClosed() {
879        // Make sure the alpha on mode options ellipse is reset when mode drawer
880        // is closed.
881        mModeOptionsToggle.setAlpha(1f);
882        mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
883    }
884
885    /**
886     * Called when the back key is pressed.
887     *
888     * @return Whether the UI responded to the key event.
889     */
890    public boolean onBackPressed() {
891        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
892            return mFilmstripLayout.onBackPressed();
893        } else {
894            return mModeListView.onBackPressed();
895        }
896    }
897
898    /**
899     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
900     * listens to SurfaceTexture changes. In addition, listeners are set on
901     * dependent app ui elements.
902     *
903     * @param previewStatusListener the listener that gets notified when SurfaceTexture
904     *                              changes
905     */
906    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
907        mPreviewStatusListener = previewStatusListener;
908        if (mPreviewStatusListener != null) {
909            onPreviewListenerChanged();
910        }
911    }
912
913    /**
914     * When the PreviewStatusListener changes, listeners need to be
915     * set on the following app ui elements:
916     * {@link com.android.camera.ui.PreviewOverlay},
917     * {@link com.android.camera.ui.BottomBar},
918     * {@link com.android.camera.ui.IndicatorIconController}.
919     */
920    private void onPreviewListenerChanged() {
921        // Set a listener for recognizing preview gestures.
922        GestureDetector.OnGestureListener gestureListener
923            = mPreviewStatusListener.getGestureListener();
924        if (gestureListener != null) {
925            mPreviewOverlay.setGestureListener(gestureListener);
926        }
927        View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
928        if (touchListener != null) {
929            mPreviewOverlay.setTouchListener(touchListener);
930        }
931
932        mTextureViewHelper.setAutoAdjustTransform(
933                mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
934    }
935
936    /**
937     * This method should be called in onCameraOpened.  It defines CameraAppUI
938     * specific changes that depend on the camera or camera settings.
939     */
940    public void onChangeCamera() {
941        ModuleController moduleController = mController.getCurrentModuleController();
942        applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec());
943
944        if (mIndicatorIconController != null) {
945            // Sync the settings state with the indicator state.
946            mIndicatorIconController.syncIndicators();
947        }
948    }
949
950    /**
951     * Adds a listener to receive callbacks when preview area changes.
952     */
953    public void addPreviewAreaChangedListener(
954            PreviewStatusListener.PreviewAreaChangedListener listener) {
955        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
956    }
957
958    /**
959     * Removes a listener that receives callbacks when preview area changes.
960     */
961    public void removePreviewAreaChangedListener(
962            PreviewStatusListener.PreviewAreaChangedListener listener) {
963        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
964    }
965
966    /**
967     * This inflates generic_module layout, which contains all the shared views across
968     * modules. Then each module inflates their own views in the given view group. For
969     * now, this is called every time switching from a not-yet-refactored module to a
970     * refactored module. In the future, this should only need to be done once per app
971     * start.
972     */
973    public void prepareModuleUI() {
974        mCameraRootView.removeAllViews();
975        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
976                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
977        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
978
979        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
980        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
981        mTextureViewHelper = new TextureViewHelper(mTextureView);
982        mTextureViewHelper.setSurfaceTextureListener(this);
983        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
984
985        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
986        int unpressedColor = mController.getAndroidContext().getResources()
987            .getColor(R.color.bottombar_unpressed);
988        setBottomBarColor(unpressedColor);
989        int pressedColor = mController.getAndroidContext().getResources()
990            .getColor(R.color.bottombar_pressed);
991        setBottomBarPressedColor(pressedColor);
992
993        mModeOptionsOverlay
994            = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
995        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeOptionsOverlay);
996
997        // Sets the visibility of the bottom bar and the mode options.
998        resetBottomControls(mController.getCurrentModuleController(),
999            mController.getCurrentModuleIndex());
1000
1001        mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1002        addShutterListener(mController.getCurrentModuleController());
1003        addShutterListener(mModeOptionsOverlay);
1004
1005        mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
1006        mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
1007
1008        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
1009        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
1010        mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
1011
1012        mCaptureOverlay = (CaptureAnimationOverlay)
1013                mCameraRootView.findViewById(R.id.capture_overlay);
1014        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
1015        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
1016        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
1017
1018        if (mIndicatorIconController == null) {
1019            mIndicatorIconController =
1020                new IndicatorIconController(mController, mAppRootView);
1021        }
1022        mIndicatorIconController.setListener(mModeOptionsOverlay);
1023
1024        mController.getButtonManager().load(mCameraRootView);
1025        mController.getButtonManager().setListener(mIndicatorIconController);
1026        mController.getSettingsManager().addListener(mIndicatorIconController);
1027
1028        mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1029        mBottomBar.addOnLayoutChangeListener(mBottomBarLayoutChangeListener);
1030        mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
1031        mTutorialsPlaceholder = (FrameLayout) mCameraRootView
1032                .findViewById(R.id.tutorials_placeholder);
1033
1034        mTextureViewHelper.addPreviewAreaSizeChangedListener(
1035                new PreviewStatusListener.PreviewAreaChangedListener() {
1036                    @Override
1037                    public void onPreviewAreaChanged(RectF previewArea) {
1038                        if (mPreviewStatusListener != null &&
1039                                mPreviewStatusListener.shouldAutoAdjustBottomBar()) {
1040                            mBottomBar.onPreviewAreaChanged(previewArea);
1041                        } else {
1042                            mPeekView.setTranslationX(previewArea.right - mAppRootView.getRight());
1043                        }
1044                    }
1045                });
1046
1047        mBottomBar.setAdjustPreviewAreaListener(new BottomBar.AdjustPreviewAreaListener() {
1048            @Override
1049            public void fitAndCenterPreviewAreaInRect(RectF rect) {
1050                mPeekView.setTranslationX(0f);
1051                if (mPreviewStatusListener != null &&
1052                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1053                    mTextureViewHelper.centerPreviewInRect(rect);
1054                }
1055            }
1056
1057            @Override
1058            public void fitAndAlignBottomInRect(RectF rect) {
1059                mPeekView.setTranslationX(0f);
1060                if (mPreviewStatusListener != null &&
1061                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1062                    mTextureViewHelper.alignBottomInRect(rect);
1063                }
1064            }
1065
1066            @Override
1067            public void fitAndAlignRightInRect(RectF rect) {
1068                mPeekView.setTranslationX(rect.right - mAppRootView.getRight());
1069                if (mPreviewStatusListener != null &&
1070                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1071                    mTextureViewHelper.alignRightInRect(rect);
1072                }
1073            }
1074        });
1075    }
1076
1077    /**
1078     * Called indirectly from each module in their initialization to get a view group
1079     * to inflate the module specific views in.
1080     *
1081     * @return a view group for modules to attach views to
1082     */
1083    public FrameLayout getModuleRootView() {
1084        // TODO: Change it to mModuleUI when refactor is done
1085        return mCameraRootView;
1086    }
1087
1088    /**
1089     * Remove all the module specific views.
1090     */
1091    public void clearModuleUI() {
1092        if (mModuleUI != null) {
1093            mModuleUI.removeAllViews();
1094        }
1095        removeShutterListener(mController.getCurrentModuleController());
1096        mTutorialsPlaceholder.removeAllViews();
1097
1098        mPreviewStatusListener = null;
1099        mPreviewOverlay.reset();
1100        mFocusOverlay.setVisibility(View.INVISIBLE);
1101    }
1102
1103    /**
1104     * Gets called when preview is ready to start. It sets up one shot preview callback
1105     * in order to receive a callback when the preview frame is available, so that
1106     * the preview cover can be hidden to reveal preview.
1107     *
1108     * An alternative for getting the timing to hide preview cover is through
1109     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1110     * which is less accurate but therefore is the fallback for modules that manage
1111     * their own preview callbacks (as setting one preview callback will override
1112     * any other installed preview callbacks), or use camera2 API.
1113     */
1114    public void onPreviewReadyToStart() {
1115        if (mModeCoverState == COVER_SHOWN) {
1116            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1117            mController.setupOneShotPreviewListener();
1118        }
1119    }
1120
1121    /**
1122     * Gets called when preview is started.
1123     */
1124    public void onPreviewStarted() {
1125        if (mModeCoverState == COVER_SHOWN) {
1126            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1127        }
1128    }
1129
1130    /**
1131     * Gets notified when next preview frame comes in.
1132     */
1133    public void onNewPreviewFrame() {
1134        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1135        hideModeCover();
1136        mModeCoverState = COVER_HIDDEN;
1137    }
1138
1139    /**
1140     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1141     *
1142     * @param modeIndex mode index of the selected mode
1143     */
1144    @Override
1145    public void onModeSelected(int modeIndex) {
1146        mHideCoverRunnable = new Runnable() {
1147            @Override
1148            public void run() {
1149                mModeListView.startModeSelectionAnimation();
1150            }
1151        };
1152        mModeCoverState = COVER_SHOWN;
1153
1154        int lastIndex = mController.getCurrentModuleIndex();
1155        mController.onModeSelected(modeIndex);
1156        int currentIndex = mController.getCurrentModuleIndex();
1157
1158        if (lastIndex == currentIndex) {
1159            hideModeCover();
1160        }
1161    }
1162
1163    @Override
1164    public void onSettingsSelected() {
1165        mController.onSettingsSelected();
1166    }
1167
1168    @Override
1169    public int getCurrentModeIndex() {
1170        return mController.getCurrentModuleIndex();
1171    }
1172
1173    /********************** Capture animation **********************/
1174    /* TODO: This session is subject to UX changes. In addition to the generic
1175       flash animation and post capture animation, consider designating a parameter
1176       for specifying the type of animation, as well as an animation finished listener
1177       so that modules can have more knowledge of the status of the animation. */
1178
1179    /**
1180     * Starts the filmstrip peek animation.
1181     *
1182     * @param bitmap The bitmap to show.
1183     * @param strong Whether the animation shows more portion of the bitmap or
1184     *               not.
1185     */
1186    public void startPeekAnimation(Bitmap bitmap, boolean strong) {
1187        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1188            return;
1189        }
1190        mPeekView.startPeekAnimation(bitmap, strong);
1191    }
1192
1193    /**
1194     * Starts the pre-capture animation.
1195     */
1196    public void startPreCaptureAnimation() {
1197        mCaptureOverlay.startFlashAnimation();
1198    }
1199
1200    /**
1201     * Cancels the pre-capture animation.
1202     */
1203    public void cancelPreCaptureAnimation() {
1204        mAnimationManager.cancelAnimations();
1205    }
1206
1207    /**
1208     * Cancels the post-capture animation.
1209     */
1210    public void cancelPostCaptureAnimation() {
1211        mAnimationManager.cancelAnimations();
1212    }
1213
1214    public FilmstripContentPanel getFilmstripContentPanel() {
1215        return mFilmstripPanel;
1216    }
1217
1218    /**
1219     * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the
1220     * bottom of the filmstrip.
1221     */
1222    public BottomPanel getFilmstripBottomControls() {
1223        return mFilmstripBottomControls;
1224    }
1225
1226    /**
1227     * @param listener The listener for bottom controls.
1228     */
1229    public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) {
1230        mFilmstripBottomControls.setListener(listener);
1231    }
1232
1233    /***************************SurfaceTexture Api and Listener*********************************/
1234
1235    /**
1236     * Return the shared surface texture.
1237     */
1238    public SurfaceTexture getSurfaceTexture() {
1239        return mSurface;
1240    }
1241
1242    /**
1243     * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1244     */
1245    public int getSurfaceWidth() {
1246        return mSurfaceWidth;
1247    }
1248
1249    /**
1250     * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1251     */
1252    public int getSurfaceHeight() {
1253        return mSurfaceHeight;
1254    }
1255
1256    @Override
1257    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1258        mSurface = surface;
1259        mSurfaceWidth = width;
1260        mSurfaceHeight = height;
1261        Log.v(TAG, "SurfaceTexture is available");
1262        if (mPreviewStatusListener != null) {
1263            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1264        }
1265    }
1266
1267    @Override
1268    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1269        mSurface = surface;
1270        mSurfaceWidth = width;
1271        mSurfaceHeight = height;
1272        if (mPreviewStatusListener != null) {
1273            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1274        }
1275    }
1276
1277    @Override
1278    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1279        mSurface = null;
1280        Log.v(TAG, "SurfaceTexture is destroyed");
1281        if (mPreviewStatusListener != null) {
1282            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1283        }
1284        return false;
1285    }
1286
1287    @Override
1288    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1289        mSurface = surface;
1290        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1291            CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1292            hideModeCover();
1293            mModeCoverState = COVER_HIDDEN;
1294        }
1295        if (mPreviewStatusListener != null) {
1296            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1297        }
1298    }
1299
1300    /****************************Grid lines api ******************************/
1301
1302    /**
1303     * Show a set of evenly spaced lines over the preview.  The number
1304     * of lines horizontally and vertically is determined by
1305     * {@link com.android.camera.ui.GridLines}.
1306     */
1307    public void showGridLines() {
1308        if (mGridLines != null) {
1309            mGridLines.setVisibility(View.VISIBLE);
1310        }
1311    }
1312
1313    /**
1314     * Hide the set of evenly spaced grid lines overlaying the preview.
1315     */
1316    public void hideGridLines() {
1317        if (mGridLines != null) {
1318            mGridLines.setVisibility(View.INVISIBLE);
1319        }
1320    }
1321
1322    /**
1323     * Return a callback which shows or hide the preview grid lines
1324     * depending on whether the grid lines setting is set on.
1325     */
1326    public ButtonManager.ButtonCallback getGridLinesCallback() {
1327        return new ButtonManager.ButtonCallback() {
1328            @Override
1329            public void onStateChanged(int state) {
1330                if (mController.getSettingsManager().areGridLinesOn()) {
1331                    showGridLines();
1332                } else {
1333                    hideGridLines();
1334                }
1335            }
1336        };
1337    }
1338
1339    /***************************Mode options api *****************************/
1340
1341    /**
1342     * Set the mode options visible.
1343     */
1344    public void showModeOptions() {
1345        mModeOptionsOverlay.setVisibility(View.VISIBLE);
1346    }
1347
1348    /**
1349     * Set the mode options invisible.  This is necessary for modes
1350     * that don't show a bottom bar for the capture UI.
1351     */
1352    public void hideModeOptions() {
1353        mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1354    }
1355
1356    /****************************Bottom bar api ******************************/
1357
1358    /**
1359     * Sets up the bottom bar and mode options with the correct
1360     * shutter button and visibility based on the current module.
1361     */
1362    public void resetBottomControls(ModuleController module, int moduleIndex) {
1363        if (areBottomControlsUsed(module)) {
1364            setBottomBarShutterIcon(moduleIndex);
1365        }
1366    }
1367
1368    /**
1369     * Show or hide the mode options and bottom bar, based on
1370     * whether the current module is using the bottom bar.  Returns
1371     * whether the mode options and bottom bar are used.
1372     */
1373    private boolean areBottomControlsUsed(ModuleController module) {
1374        if (module.isUsingBottomBar()) {
1375            showBottomBar();
1376            showModeOptions();
1377            return true;
1378        } else {
1379            hideBottomBar();
1380            hideModeOptions();
1381            return false;
1382        }
1383    }
1384
1385    /**
1386     * Set the bottom bar visible.
1387     */
1388    public void showBottomBar() {
1389        mBottomBar.setVisibility(View.VISIBLE);
1390    }
1391
1392    /**
1393     * Set the bottom bar invisible.
1394     */
1395    public void hideBottomBar() {
1396        mBottomBar.setVisibility(View.INVISIBLE);
1397    }
1398
1399    /**
1400     * Sets the color of the bottom bar.
1401     */
1402    public void setBottomBarColor(int colorId) {
1403        mBottomBar.setBackgroundColor(colorId);
1404    }
1405
1406    /**
1407     * Sets the pressed color of the bottom bar.
1408     */
1409    public void setBottomBarPressedColor(int colorId) {
1410        mBottomBar.setBackgroundPressedColor(colorId);
1411    }
1412
1413    /**
1414     * Sets the shutter button icon on the bottom bar, based on
1415     * the mode index.
1416     */
1417    public void setBottomBarShutterIcon(int modeIndex) {
1418        int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1419            mController.getAndroidContext());
1420        mBottomBar.setShutterButtonIcon(shutterIconId);
1421    }
1422
1423    public void animateBottomBarToCircle(int shutterIconId) {
1424        mBottomBar.animateToCircle(shutterIconId);
1425    }
1426
1427    public void animateBottomBarToFullSize(int shutterIconId) {
1428        mBottomBar.animateToFullSize(shutterIconId);
1429    }
1430
1431    /**
1432     * Set the visibility of the bottom bar.
1433     */
1434    // TODO: needed for when panorama is managed by the generic module ui.
1435    public void setBottomBarVisible(boolean visible) {
1436        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1437    }
1438
1439    /**
1440     * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button.
1441     */
1442    public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1443        mShutterButton.addOnShutterButtonListener(listener);
1444    }
1445
1446    /**
1447     * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
1448     */
1449    public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) {
1450        mShutterButton.removeOnShutterButtonListener(listener);
1451    }
1452
1453    /**
1454     * Performs a transition to the capture layout of the bottom bar.
1455     */
1456    public void transitionToCapture() {
1457        ModuleController moduleController = mController.getCurrentModuleController();
1458        applyModuleSpecs(moduleController.getHardwareSpec(),
1459            moduleController.getBottomBarSpec());
1460        mBottomBar.transitionToCapture();
1461    }
1462
1463    /**
1464     * Performs a transition to the global intent layout.
1465     */
1466    public void transitionToIntentCaptureLayout() {
1467        ModuleController moduleController = mController.getCurrentModuleController();
1468        applyModuleSpecs(moduleController.getHardwareSpec(),
1469            moduleController.getBottomBarSpec());
1470        mBottomBar.transitionToIntentCaptureLayout();
1471    }
1472
1473    /**
1474     * Performs a transition to the global intent review layout.
1475     */
1476    public void transitionToIntentReviewLayout() {
1477        ModuleController moduleController = mController.getCurrentModuleController();
1478        applyModuleSpecs(moduleController.getHardwareSpec(),
1479            moduleController.getBottomBarSpec());
1480        mBottomBar.transitionToIntentReviewLayout();
1481    }
1482
1483    /**
1484     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1485     * to the bottom bar mode options based on limitations from a
1486     * {@link com.android.camera.hardware.HardwareSpec}.
1487     *
1488     * Options not supported by the hardware are either hidden
1489     * or disabled, depending on the option.
1490     *
1491     * Otherwise, the option is fully enabled and clickable.
1492     */
1493    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1494           final BottomBarUISpec bottomBarSpec) {
1495        if (hardwareSpec == null || bottomBarSpec == null) {
1496            return;
1497        }
1498
1499        ButtonManager buttonManager = mController.getButtonManager();
1500        SettingsManager settingsManager = mController.getSettingsManager();
1501
1502        /** Standard mode options */
1503        if (hardwareSpec.isFrontCameraSupported()) {
1504            if (bottomBarSpec.enableCamera) {
1505                buttonManager.enableButton(ButtonManager.BUTTON_CAMERA,
1506                    bottomBarSpec.cameraCallback);
1507            } else {
1508                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1509            }
1510        } else {
1511            // Hide camera icon if front camera not available.
1512            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1513        }
1514
1515        boolean flashBackCamera = mController.getSettingsManager().getBoolean(
1516            SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA);
1517        if (bottomBarSpec.hideFlash || !flashBackCamera) {
1518            buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
1519        } else {
1520            if (hardwareSpec.isFlashSupported()) {
1521                if (bottomBarSpec.enableFlash) {
1522                    buttonManager.enableButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1523                } else if (bottomBarSpec.enableTorchFlash) {
1524                    buttonManager.enableButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1525                } else {
1526                    buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1527                }
1528            } else {
1529                // Disable flash icon if not supported by the hardware.
1530                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1531            }
1532        }
1533
1534        if (bottomBarSpec.hideHdr || mIsCaptureIntent) {
1535            // Force hide hdr or hdr plus icon.
1536            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1537        } else {
1538            if (hardwareSpec.isHdrPlusSupported()) {
1539                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1540                    buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS,
1541                        bottomBarSpec.hdrCallback);
1542                } else {
1543                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1544                }
1545            } else if (hardwareSpec.isHdrSupported()) {
1546                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1547                    buttonManager.enableButton(ButtonManager.BUTTON_HDR,
1548                        bottomBarSpec.hdrCallback);
1549                } else {
1550                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1551                }
1552            } else {
1553                // Hide hdr plus or hdr icon if neither are supported.
1554                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1555            }
1556        }
1557
1558        if (bottomBarSpec.hideGridLines) {
1559            // Force hide grid lines icon.
1560            buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
1561            hideGridLines();
1562        } else {
1563            if (bottomBarSpec.enableGridLines) {
1564                buttonManager.enableButton(ButtonManager.BUTTON_GRID_LINES,
1565                    bottomBarSpec.gridLinesCallback != null ?
1566                    bottomBarSpec.gridLinesCallback : getGridLinesCallback());
1567            } else {
1568                buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
1569                hideGridLines();
1570            }
1571        }
1572
1573        if (bottomBarSpec.enablePanoOrientation
1574                && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
1575            buttonManager.enableButton(ButtonManager.BUTTON_PANO_ORIENTATION,
1576                    bottomBarSpec.panoOrientationCallback);
1577        } else {
1578            buttonManager.hideButton(ButtonManager.BUTTON_PANO_ORIENTATION);
1579        }
1580
1581        /** Intent UI */
1582        if (bottomBarSpec.showCancel) {
1583            buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL,
1584                bottomBarSpec.cancelCallback);
1585        }
1586        if (bottomBarSpec.showDone) {
1587            buttonManager.enablePushButton(ButtonManager.BUTTON_DONE,
1588                bottomBarSpec.doneCallback);
1589        }
1590        if (bottomBarSpec.showRetake) {
1591            buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE,
1592                bottomBarSpec.retakeCallback);
1593        }
1594        if (bottomBarSpec.showReview) {
1595            buttonManager.enablePushButton(ButtonManager.BUTTON_REVIEW,
1596               bottomBarSpec.reviewCallback,
1597               R.drawable.ic_play);
1598        }
1599    }
1600}
1601