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