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