CameraAppUI.java revision d4c3333c99ece20b3125e86e1e3f6105b2bc9bdd
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.accessibilityservice.AccessibilityServiceInfo;
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Matrix;
25import android.graphics.RectF;
26import android.graphics.SurfaceTexture;
27import android.hardware.display.DisplayManager;
28import android.util.CameraPerformanceTracker;
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.view.accessibility.AccessibilityManager;
37import android.widget.FrameLayout;
38
39import com.android.camera.AnimationManager;
40import com.android.camera.ButtonManager;
41import com.android.camera.CaptureLayoutHelper;
42import com.android.camera.ShutterButton;
43import com.android.camera.TextureViewHelper;
44import com.android.camera.debug.Log;
45import com.android.camera.filmstrip.FilmstripContentPanel;
46import com.android.camera.hardware.HardwareSpec;
47import com.android.camera.module.ModuleController;
48import com.android.camera.settings.SettingsManager;
49import com.android.camera.ui.AbstractTutorialOverlay;
50import com.android.camera.ui.BottomBar;
51import com.android.camera.ui.BottomBarModeOptionsWrapper;
52import com.android.camera.ui.CaptureAnimationOverlay;
53import com.android.camera.ui.GridLines;
54import com.android.camera.ui.MainActivityLayout;
55import com.android.camera.ui.ModeListView;
56import com.android.camera.ui.ModeTransitionView;
57import com.android.camera.ui.PreviewOverlay;
58import com.android.camera.ui.PreviewStatusListener;
59import com.android.camera.util.ApiHelper;
60import com.android.camera.util.CameraUtil;
61import com.android.camera.util.Gusterpolator;
62import com.android.camera.util.PhotoSphereHelper;
63import com.android.camera.widget.Cling;
64import com.android.camera.widget.FilmstripLayout;
65import com.android.camera.widget.IndicatorIconController;
66import com.android.camera.widget.ModeOptionsOverlay;
67import com.android.camera.widget.PeekView;
68import com.android.camera2.R;
69
70import java.util.List;
71
72/**
73 * CameraAppUI centralizes control of views shared across modules. Whereas module
74 * specific views will be handled in each Module UI. For example, we can now
75 * bring the flash animation and capture animation up from each module to app
76 * level, as these animations are largely the same for all modules.
77 *
78 * This class also serves to disambiguate touch events. It recognizes all the
79 * swipe gestures that happen on the preview by attaching a touch listener to
80 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
81 * of how swipe from each direction should be handled, it can then redirect these
82 * events to appropriate recipient views.
83 */
84public class CameraAppUI implements ModeListView.ModeSwitchListener,
85                                    TextureView.SurfaceTextureListener,
86                                    ModeListView.ModeListOpenListener,
87                                    SettingsManager.OnSettingChangedListener,
88                                    ShutterButton.OnShutterButtonListener {
89
90    /**
91     * The bottom controls on the filmstrip.
92     */
93    public static interface BottomPanel {
94        /** Values for the view state of the button. */
95        public final int VIEWER_NONE = 0;
96        public final int VIEWER_PHOTO_SPHERE = 1;
97        public final int VIEWER_REFOCUS = 2;
98        public final int VIEWER_OTHER = 3;
99
100        /**
101         * Sets a new or replaces an existing listener for bottom control events.
102         */
103        void setListener(Listener listener);
104
105        /**
106         * Sets cling for external viewer button.
107         */
108        void setClingForViewer(int viewerType, Cling cling);
109
110        /**
111         * Clears cling for external viewer button.
112         */
113        void clearClingForViewer(int viewerType);
114
115        /**
116         * Returns a cling for the specified viewer type.
117         */
118        Cling getClingForViewer(int viewerType);
119
120        /**
121         * Set if the bottom controls are visible.
122         * @param visible {@code true} if visible.
123         */
124        void setVisible(boolean visible);
125
126        /**
127         * @param visible Whether the button is visible.
128         */
129        void setEditButtonVisibility(boolean visible);
130
131        /**
132         * @param enabled Whether the button is enabled.
133         */
134        void setEditEnabled(boolean enabled);
135
136        /**
137         * Sets the visibility of the view-photosphere button.
138         *
139         * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE},
140         *            {@link #VIEWER_REFOCUS}.
141         */
142        void setViewerButtonVisibility(int state);
143
144        /**
145         * @param enabled Whether the button is enabled.
146         */
147        void setViewEnabled(boolean enabled);
148
149        /**
150         * @param enabled Whether the button is enabled.
151         */
152        void setTinyPlanetEnabled(boolean enabled);
153
154        /**
155         * @param visible Whether the button is visible.
156         */
157        void setDeleteButtonVisibility(boolean visible);
158
159        /**
160         * @param enabled Whether the button is enabled.
161         */
162        void setDeleteEnabled(boolean enabled);
163
164        /**
165         * @param visible Whether the button is visible.
166         */
167        void setShareButtonVisibility(boolean visible);
168
169        /**
170         * @param enabled Whether the button is enabled.
171         */
172        void setShareEnabled(boolean enabled);
173
174        /**
175         * Sets the texts for progress UI.
176         *
177         * @param text The text to show.
178         */
179        void setProgressText(CharSequence text);
180
181        /**
182         * Sets the progress.
183         *
184         * @param progress The progress value. Should be between 0 and 100.
185         */
186        void setProgress(int progress);
187
188        /**
189         * Replaces the progress UI with an error message.
190         */
191        void showProgressError(CharSequence message);
192
193        /**
194         * Hide the progress error message.
195         */
196        void hideProgressError();
197
198        /**
199         * Shows the progress.
200         */
201        void showProgress();
202
203        /**
204         * Hides the progress.
205         */
206        void hideProgress();
207
208        /**
209         * Shows the controls.
210         */
211        void showControls();
212
213        /**
214         * Hides the controls.
215         */
216        void hideControls();
217
218        /**
219         * Classes implementing this interface can listen for events on the bottom
220         * controls.
221         */
222        public static interface Listener {
223            /**
224             * Called when the user pressed the "view" button to e.g. view a photo
225             * sphere or RGBZ image.
226             */
227            public void onExternalViewer();
228
229            /**
230             * Called when the "edit" button is pressed.
231             */
232            public void onEdit();
233
234            /**
235             * Called when the "tiny planet" button is pressed.
236             */
237            public void onTinyPlanet();
238
239            /**
240             * Called when the "delete" button is pressed.
241             */
242            public void onDelete();
243
244            /**
245             * Called when the "share" button is pressed.
246             */
247            public void onShare();
248
249            /**
250             * Called when the progress error message is clicked.
251             */
252            public void onProgressErrorClicked();
253        }
254    }
255
256    /**
257     * BottomBarUISpec provides a structure for modules
258     * to specify their ideal bottom bar mode options layout.
259     *
260     * Once constructed by a module, this class should be
261     * treated as read only.
262     *
263     * The application then edits this spec according to
264     * hardware limitations and displays the final bottom
265     * bar ui.
266     */
267    public static class BottomBarUISpec {
268        /** Mode options UI */
269
270        /**
271         * Set true if the camera option should be enabled.
272         * If not set or false, and multiple cameras are supported,
273         * the camera option will be disabled.
274         *
275         * If multiple cameras are not supported, this preference
276         * is ignored and the camera option will not be visible.
277         */
278        public boolean enableCamera;
279
280        /**
281         * Set true if the camera option should not be visible, regardless
282         * of hardware limitations.
283         */
284        public boolean hideCamera;
285
286        /**
287         * Set true if the photo flash option should be enabled.
288         * If not set or false, the photo flash option will be
289         * disabled.
290         *
291         * If the hardware does not support multiple flash values,
292         * this preference is ignored and the flash option will
293         * be disabled.  It will not be made invisible in order to
294         * preserve a consistent experience across devices and between
295         * front and back cameras.
296         */
297        public boolean enableFlash;
298
299        /**
300         * Set true if the video flash option should be enabled.
301         * Same disable rules apply as the photo flash option.
302         */
303        public boolean enableTorchFlash;
304
305        /**
306         * Set true if flash should not be visible, regardless of
307         * hardware limitations.
308         */
309        public boolean hideFlash;
310
311        /**
312         * Set true if the hdr/hdr+ option should be enabled.
313         * If not set or false, the hdr/hdr+ option will be disabled.
314         *
315         * Hdr or hdr+ will be chosen based on hardware limitations,
316         * with hdr+ prefered.
317         *
318         * If hardware supports neither hdr nor hdr+, then the hdr/hdr+
319         * will not be visible.
320         */
321        public boolean enableHdr;
322
323        /**
324         * Set true if hdr/hdr+ should not be visible, regardless of
325         * hardware limitations.
326         */
327        public boolean hideHdr;
328
329        /**
330         * Set true if grid lines should be visible.  Not setting this
331         * causes grid lines to be disabled.  This option is agnostic to
332         * the hardware.
333         */
334        public boolean enableGridLines;
335
336        /**
337         * Set true if grid lines should not be visible.
338         */
339        public boolean hideGridLines;
340
341        /**
342         * Set true if the panorama orientation option should be visible.
343         *
344         * This option is not constrained by hardware limitations.
345         */
346        public boolean enablePanoOrientation;
347
348        public boolean enableExposureCompensation;
349
350        /** Intent UI */
351
352        /**
353         * Set true if the intent ui cancel option should be visible.
354         */
355        public boolean showCancel;
356        /**
357         * Set true if the intent ui done option should be visible.
358         */
359        public boolean showDone;
360        /**
361         * Set true if the intent ui retake option should be visible.
362         */
363        public boolean showRetake;
364        /**
365         * Set true if the intent ui review option should be visible.
366         */
367        public boolean showReview;
368
369        /** Mode options callbacks */
370
371        /**
372         * A {@link com.android.camera.ButtonManager.ButtonCallback}
373         * that will be executed when the camera option is pressed. This
374         * callback can be null.
375         */
376        public ButtonManager.ButtonCallback cameraCallback;
377
378        /**
379         * A {@link com.android.camera.ButtonManager.ButtonCallback}
380         * that will be executed when the flash option is pressed. This
381         * callback can be null.
382         */
383        public ButtonManager.ButtonCallback flashCallback;
384
385        /**
386         * A {@link com.android.camera.ButtonManager.ButtonCallback}
387         * that will be executed when the hdr/hdr+ option is pressed. This
388         * callback can be null.
389         */
390        public ButtonManager.ButtonCallback hdrCallback;
391
392        /**
393         * A {@link com.android.camera.ButtonManager.ButtonCallback}
394         * that will be executed when the grid lines option is pressed. This
395         * callback can be null.
396         */
397        public ButtonManager.ButtonCallback gridLinesCallback;
398
399        /**
400         * A {@link com.android.camera.ButtonManager.ButtonCallback}
401         * that will execute when the panorama orientation option is pressed.
402         * This callback can be null.
403         */
404        public ButtonManager.ButtonCallback panoOrientationCallback;
405
406        /** Intent UI callbacks */
407
408        /**
409         * A {@link android.view.View.OnClickListener} that will execute
410         * when the cancel option is pressed. This callback can be null.
411         */
412        public View.OnClickListener cancelCallback;
413
414        /**
415         * A {@link android.view.View.OnClickListener} that will execute
416         * when the done option is pressed. This callback can be null.
417         */
418        public View.OnClickListener doneCallback;
419
420        /**
421         * A {@link android.view.View.OnClickListener} that will execute
422         * when the retake option is pressed. This callback can be null.
423         */
424        public View.OnClickListener retakeCallback;
425
426        /**
427         * A {@link android.view.View.OnClickListener} that will execute
428         * when the review option is pressed. This callback can be null.
429         */
430        public View.OnClickListener reviewCallback;
431
432        /**
433         * A ExposureCompensationSetCallback that will execute
434         * when an expsosure button is pressed. This callback can be null.
435         */
436        public interface ExposureCompensationSetCallback {
437            public void setExposure(int value);
438        }
439        public ExposureCompensationSetCallback exposureCompensationSetCallback;
440
441        /**
442         * Exposure compensation parameters.
443         */
444        public int minExposureCompensation;
445        public int maxExposureCompensation;
446        public float exposureCompensationStep;
447    }
448
449
450    private final static Log.Tag TAG = new Log.Tag("CameraAppUI");
451
452    private final AppController mController;
453    private final boolean mIsCaptureIntent;
454    private final AnimationManager mAnimationManager;
455
456    // Swipe states:
457    private final static int IDLE = 0;
458    private final static int SWIPE_UP = 1;
459    private final static int SWIPE_DOWN = 2;
460    private final static int SWIPE_LEFT = 3;
461    private final static int SWIPE_RIGHT = 4;
462    private boolean mSwipeEnabled = true;
463
464    // Shared Surface Texture properities.
465    private SurfaceTexture mSurface;
466    private int mSurfaceWidth;
467    private int mSurfaceHeight;
468
469    // Touch related measures:
470    private final int mSlop;
471    private final static int SWIPE_TIME_OUT_MS = 500;
472
473    // Mode cover states:
474    private final static int COVER_HIDDEN = 0;
475    private final static int COVER_SHOWN = 1;
476    private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
477    private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3;
478
479    // App level views:
480    private final FrameLayout mCameraRootView;
481    private final ModeTransitionView mModeTransitionView;
482    private final MainActivityLayout mAppRootView;
483    private final ModeListView mModeListView;
484    private final FilmstripLayout mFilmstripLayout;
485    private TextureView mTextureView;
486    private FrameLayout mModuleUI;
487    private ShutterButton mShutterButton;
488    private BottomBar mBottomBar;
489    private ModeOptionsOverlay mModeOptionsOverlay;
490    private IndicatorIconController mIndicatorIconController;
491    private View mFocusOverlay;
492    private FrameLayout mTutorialsPlaceHolderWrapper;
493    private BottomBarModeOptionsWrapper mIndicatorBottomBarWrapper;
494    private TextureViewHelper mTextureViewHelper;
495    private final GestureDetector mGestureDetector;
496    private DisplayManager.DisplayListener mDisplayListener;
497    private int mLastRotation;
498    private int mSwipeState = IDLE;
499    private PreviewOverlay mPreviewOverlay;
500    private GridLines mGridLines;
501    private CaptureAnimationOverlay mCaptureOverlay;
502    private PreviewStatusListener mPreviewStatusListener;
503    private int mModeCoverState = COVER_HIDDEN;
504    private final FilmstripBottomPanel mFilmstripBottomControls;
505    private final FilmstripContentPanel mFilmstripPanel;
506    private Runnable mHideCoverRunnable;
507    private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
508            = new View.OnLayoutChangeListener() {
509        @Override
510        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
511                int oldTop, int oldRight, int oldBottom) {
512            if (mPreviewStatusListener != null) {
513                mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
514                        oldTop, oldRight, oldBottom);
515            }
516        }
517    };
518    private View mModeOptionsToggle;
519    private final PeekView mPeekView;
520    private final CaptureLayoutHelper mCaptureLayoutHelper;
521    private boolean mAccessibilityEnabled;
522    private final View mAccessibilityAffordances;
523
524    /**
525     * Provides current preview frame and the controls/overlay from the module that
526     * are shown on top of the preview.
527     */
528    public interface CameraModuleScreenShotProvider {
529        /**
530         * Returns the current preview frame down-sampled using the given down-sample
531         * factor.
532         *
533         * @param downSampleFactor the down sample factor for down sampling the
534         *                         preview frame. (e.g. a down sample factor of
535         *                         2 means to scale down the preview frame to 1/2
536         *                         the width and height.)
537         * @return down-sampled preview frame
538         */
539        public Bitmap getPreviewFrame(int downSampleFactor);
540
541        /**
542         * @return the controls and overlays that are currently showing on top of
543         *         the preview drawn into a bitmap with no scaling applied.
544         */
545        public Bitmap getPreviewOverlayAndControls();
546    }
547
548    /**
549     * This listener gets called when the size of the window (excluding the system
550     * decor such as status bar and nav bar) has changed.
551     */
552    public interface NonDecorWindowSizeChangedListener {
553        public void onNonDecorWindowSizeChanged(int width, int height, int rotation);
554    }
555
556    private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider =
557            new CameraModuleScreenShotProvider() {
558                @Override
559                public Bitmap getPreviewFrame(int downSampleFactor) {
560                    if (mCameraRootView == null || mTextureView == null) {
561                        return null;
562                    }
563                    RectF previewArea = mTextureViewHelper.getPreviewArea();
564                    // Gets the bitmap from the preview TextureView.
565                    Bitmap preview = mTextureView.getBitmap(
566                            (int) previewArea.width() / downSampleFactor,
567                            (int) previewArea.height() / downSampleFactor);
568                    return preview;
569                }
570
571                @Override
572                public Bitmap getPreviewOverlayAndControls() {
573                    Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(),
574                            mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
575                    Canvas canvas = new Canvas(overlays);
576                    mCameraRootView.draw(canvas);
577                    return overlays;
578                }
579            };
580
581    private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
582
583    public long getCoverHiddenTime() {
584        return mCoverHiddenTime;
585    }
586
587    /**
588     * This resets the preview to have no applied transform matrix.
589     */
590    public void clearPreviewTransform() {
591        mTextureViewHelper.clearTransform();
592    }
593
594    public void updatePreviewAspectRatio(float aspectRatio) {
595        mTextureViewHelper.updateAspectRatio(aspectRatio);
596    }
597
598    /**
599     * This is to support modules that calculate their own transform matrix because
600     * they need to use a transform matrix to rotate the preview.
601     *
602     * @param matrix transform matrix to be set on the TextureView
603     */
604    public void updatePreviewTransform(Matrix matrix) {
605        mTextureViewHelper.updateTransform(matrix);
606    }
607
608    public interface AnimationFinishedListener {
609        public void onAnimationFinished(boolean success);
610    }
611
612    private class MyTouchListener implements View.OnTouchListener {
613        private boolean mScaleStarted = false;
614        @Override
615        public boolean onTouch(View v, MotionEvent event) {
616            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
617                mScaleStarted = false;
618            } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
619                mScaleStarted = true;
620            }
621            return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
622        }
623    }
624
625    /**
626     * This gesture listener finds out the direction of the scroll gestures and
627     * sends them to CameraAppUI to do further handling.
628     */
629    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
630        private MotionEvent mDown;
631
632        @Override
633        public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
634            if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
635                    || mSwipeState != IDLE
636                    || mIsCaptureIntent
637                    || !mSwipeEnabled) {
638                return false;
639            }
640
641            int deltaX = (int) (ev.getX() - mDown.getX());
642            int deltaY = (int) (ev.getY() - mDown.getY());
643            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
644                if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
645                    // Calculate the direction of the swipe.
646                    if (deltaX >= Math.abs(deltaY)) {
647                        // Swipe right.
648                        setSwipeState(SWIPE_RIGHT);
649                    } else if (deltaX <= -Math.abs(deltaY)) {
650                        // Swipe left.
651                        setSwipeState(SWIPE_LEFT);
652                    }
653                }
654            }
655            return true;
656        }
657
658        private void setSwipeState(int swipeState) {
659            mSwipeState = swipeState;
660            // Notify new swipe detected.
661            onSwipeDetected(swipeState);
662        }
663
664        @Override
665        public boolean onDown(MotionEvent ev) {
666            mDown = MotionEvent.obtain(ev);
667            mSwipeState = IDLE;
668            return false;
669        }
670    }
671
672    public CameraAppUI(AppController controller, MainActivityLayout appRootView,
673            boolean isCaptureIntent) {
674        mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
675        mController = controller;
676        mIsCaptureIntent = isCaptureIntent;
677
678        mAppRootView = appRootView;
679        mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
680        mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
681        mModeTransitionView = (ModeTransitionView)
682                mAppRootView.findViewById(R.id.mode_transition_view);
683        mFilmstripBottomControls = new FilmstripBottomPanel(controller,
684                (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_panel));
685        mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
686        mGestureDetector = new GestureDetector(controller.getAndroidContext(),
687                new MyGestureListener());
688        Resources res = controller.getAndroidContext().getResources();
689        mCaptureLayoutHelper = new CaptureLayoutHelper(
690                res.getDimensionPixelSize(R.dimen.bottom_bar_height_min),
691                res.getDimensionPixelSize(R.dimen.bottom_bar_height_max),
692                res.getDimensionPixelSize(R.dimen.bottom_bar_height_optimal));
693        mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
694        if (mModeListView != null) {
695            mModeListView.setModeSwitchListener(this);
696            mModeListView.setModeListOpenListener(this);
697            mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider);
698            mModeListView.setCaptureLayoutHelper(mCaptureLayoutHelper);
699        } else {
700            Log.e(TAG, "Cannot find mode list in the view hierarchy");
701        }
702        mAnimationManager = new AnimationManager();
703        mPeekView = (PeekView) appRootView.findViewById(R.id.peek_view);
704        mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper);
705        initDisplayListener();
706        mAccessibilityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances);
707        View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button);
708        modeListToggle.setOnClickListener(new View.OnClickListener() {
709            @Override
710            public void onClick(View view) {
711                openModeList();
712            }
713        });
714        View filmstripToggle = mAppRootView.findViewById(
715                R.id.accessibility_filmstrip_toggle_button);
716        filmstripToggle.setOnClickListener(new View.OnClickListener() {
717            @Override
718            public void onClick(View view) {
719                showFilmstrip();
720            }
721        });
722    }
723
724    /**
725     * Creates a cling for the specific viewer and links the cling to the corresponding
726     * button for layout position.
727     *
728     * @param viewerType defines which viewer the cling is for.
729     */
730    public void setupClingForViewer(int viewerType) {
731        if (viewerType == BottomPanel.VIEWER_REFOCUS) {
732            FrameLayout filmstripContent = (FrameLayout) mAppRootView
733                    .findViewById(R.id.camera_filmstrip_content_layout);
734            if (filmstripContent != null) {
735                // Creates refocus cling.
736                LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
737                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
738                Cling refocusCling = (Cling) inflater.inflate(R.layout.cling_widget, null, false);
739                // Sets instruction text in the cling.
740                refocusCling.setText(mController.getAndroidContext().getResources()
741                        .getString(R.string.cling_text_for_refocus_editor_button));
742
743                // Adds cling into view hierarchy.
744                int clingWidth = mController.getAndroidContext()
745                        .getResources().getDimensionPixelSize(R.dimen.default_cling_width);
746                filmstripContent.addView(refocusCling, clingWidth,
747                        ViewGroup.LayoutParams.WRAP_CONTENT);
748                mFilmstripBottomControls.setClingForViewer(viewerType, refocusCling);
749            }
750        }
751    }
752
753    /**
754     * Clears the listeners for the cling and remove it from the view hierarchy.
755     *
756     * @param viewerType defines which viewer the cling is for.
757     */
758    public void clearClingForViewer(int viewerType) {
759        Cling clingToBeRemoved = mFilmstripBottomControls.getClingForViewer(viewerType);
760        if (clingToBeRemoved == null) {
761            // No cling is created for the specific viewer type.
762            return;
763        }
764        mFilmstripBottomControls.clearClingForViewer(viewerType);
765        clingToBeRemoved.setVisibility(View.GONE);
766        mAppRootView.removeView(clingToBeRemoved);
767    }
768
769    /**
770     * Enable or disable swipe gestures. We want to disable them e.g. while we
771     * record a video.
772     */
773    public void setSwipeEnabled(boolean enabled) {
774        mSwipeEnabled = enabled;
775        // TODO: This can be removed once we come up with a new design for handling swipe
776        // on shutter button and mode options. (More details: b/13751653)
777        mAppRootView.setSwipeEnabled(enabled);
778    }
779
780    public void onDestroy() {
781        ((DisplayManager) mController.getAndroidContext()
782                .getSystemService(Context.DISPLAY_SERVICE))
783                .unregisterDisplayListener(mDisplayListener);
784    }
785
786    /**
787     * Initializes the display listener to listen to display changes such as
788     * 180-degree rotation change, which will not have an onConfigurationChanged
789     * callback.
790     */
791    private void initDisplayListener() {
792        if (ApiHelper.HAS_DISPLAY_LISTENER) {
793            mLastRotation = CameraUtil.getDisplayRotation(mController.getAndroidContext());
794
795            mDisplayListener = new DisplayManager.DisplayListener() {
796                @Override
797                public void onDisplayAdded(int arg0) {
798                    // Do nothing.
799                }
800
801                @Override
802                public void onDisplayChanged(int displayId) {
803                    int rotation = CameraUtil.getDisplayRotation(
804                            mController.getAndroidContext());
805                    if ((rotation - mLastRotation + 360) % 360 == 180
806                            && mPreviewStatusListener != null) {
807                        mPreviewStatusListener.onPreviewFlipped();
808                        mIndicatorBottomBarWrapper.requestLayout();
809                        mModeListView.requestLayout();
810                        mTextureView.requestLayout();
811                    }
812                    mLastRotation = rotation;
813                }
814
815                @Override
816                public void onDisplayRemoved(int arg0) {
817                    // Do nothing.
818                }
819            };
820
821            ((DisplayManager) mController.getAndroidContext()
822                    .getSystemService(Context.DISPLAY_SERVICE))
823                    .registerDisplayListener(mDisplayListener, null);
824        }
825    }
826
827    /**
828     * Redirects touch events to appropriate recipient views based on swipe direction.
829     * More specifically, swipe up and swipe down will be handled by the view that handles
830     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
831     * to mode list in order to bring up mode list.
832     */
833    private void onSwipeDetected(int swipeState) {
834        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
835            // TODO: Polish quick switch after this release.
836            // Quick switch between modes.
837            int currentModuleIndex = mController.getCurrentModuleIndex();
838            final int moduleToTransitionTo =
839                    mController.getQuickSwitchToModuleId(currentModuleIndex);
840            if (currentModuleIndex != moduleToTransitionTo) {
841                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
842
843                int shadeColorId = R.color.mode_cover_default_color;
844                int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
845                        mController.getAndroidContext());
846
847                AnimationFinishedListener listener = new AnimationFinishedListener() {
848                    @Override
849                    public void onAnimationFinished(boolean success) {
850                        if (success) {
851                            mHideCoverRunnable = new Runnable() {
852                                @Override
853                                public void run() {
854                                    mModeTransitionView.startPeepHoleAnimation();
855                                }
856                            };
857                            mModeCoverState = COVER_SHOWN;
858                            // Go to new module when the previous operation is successful.
859                            mController.onModeSelected(moduleToTransitionTo);
860                        }
861                    }
862                };
863                if (mSwipeState == SWIPE_UP) {
864                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
865                } else {
866                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
867                }
868            }
869        } else if (swipeState == SWIPE_LEFT) {
870            // Pass the touch sequence to filmstrip layout.
871            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
872        } else if (swipeState == SWIPE_RIGHT) {
873            // Pass the touch to mode switcher
874            mAppRootView.redirectTouchEventsTo(mModeListView);
875        }
876    }
877
878    /**
879     * Gets called when activity resumes in preview.
880     */
881    public void resume() {
882        // Show mode theme cover until preview is ready
883        showModeCoverUntilPreviewReady();
884
885        // Hide action bar first since we are in full screen mode first, and
886        // switch the system UI to lights-out mode.
887        mFilmstripPanel.hide();
888
889        // Show UI that is meant to only be used when spoken feedback is
890        // enabled.
891        mAccessibilityEnabled = isSpokenFeedbackAccessibilityEnabled();
892        mAccessibilityAffordances.setVisibility(mAccessibilityEnabled ? View.VISIBLE : View.GONE);
893    }
894
895    /**
896     * @return Whether any spoken feedback accessibility feature is currently
897     *         enabled.
898     */
899    private boolean isSpokenFeedbackAccessibilityEnabled() {
900        AccessibilityManager accessibilityManager = (AccessibilityManager) mController
901                .getAndroidContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
902        List<AccessibilityServiceInfo> infos = accessibilityManager
903                .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
904        return infos != null && !infos.isEmpty();
905    }
906
907    /**
908     * Opens the mode list (e.g. because of the menu button being pressed) and
909     * adapts the rest of the UI.
910     */
911    public void openModeList() {
912        mModeOptionsOverlay.closeModeOptions();
913        mModeListView.onMenuPressed();
914    }
915
916    /**
917     * A cover view showing the mode theme color and mode icon will be visible on
918     * top of preview until preview is ready (i.e. camera preview is started and
919     * the first frame has been received).
920     */
921    private void showModeCoverUntilPreviewReady() {
922        int modeId = mController.getCurrentModuleIndex();
923        int colorId = R.color.mode_cover_default_color;;
924        int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext());
925        mModeTransitionView.setupModeCover(colorId, iconId);
926        mHideCoverRunnable = new Runnable() {
927            @Override
928            public void run() {
929                mModeTransitionView.hideModeCover(null);
930                showShimmyDelayed();
931            }
932        };
933        mModeCoverState = COVER_SHOWN;
934    }
935
936    private void showShimmyDelayed() {
937        if (!mIsCaptureIntent) {
938            // Show shimmy in SHIMMY_DELAY_MS
939            mModeListView.showModeSwitcherHint();
940        }
941    }
942
943    private void hideModeCover() {
944        if (mHideCoverRunnable != null) {
945            mAppRootView.post(mHideCoverRunnable);
946            mHideCoverRunnable = null;
947        }
948        mModeCoverState = COVER_HIDDEN;
949        if (mCoverHiddenTime < 0) {
950            mCoverHiddenTime = System.currentTimeMillis();
951        }
952    }
953
954
955    public void onPreviewVisiblityChanged(int visibility) {
956        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
957            setIndicatorBottomBarWrapperVisible(false);
958            mAccessibilityAffordances.setVisibility(View.GONE);
959        } else {
960            setIndicatorBottomBarWrapperVisible(true);
961            if (mAccessibilityEnabled) {
962                mAccessibilityAffordances.setVisibility(View.VISIBLE);
963            } else {
964                mAccessibilityAffordances.setVisibility(View.GONE);
965            }
966        }
967    }
968
969    /**
970     * Call to stop the preview from being rendered.
971     */
972    public void pausePreviewRendering() {
973        mTextureView.setVisibility(View.INVISIBLE);
974    }
975
976    /**
977     * Call to begin rendering the preview again.
978     */
979    public void resumePreviewRendering() {
980        mTextureView.setVisibility(View.VISIBLE);
981    }
982
983    @Override
984    public void onOpenFullScreen() {
985        // Do nothing.
986    }
987
988    @Override
989    public void onModeListOpenProgress(float progress) {
990        progress = 1 - progress;
991        float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
992        mModeOptionsToggle.setAlpha(interpolatedProgress);
993        // Change shutter button alpha linearly based on the mode list open progress:
994        // set the alpha to disabled alpha when list is fully open, to enabled alpha
995        // when the list is fully closed.
996        mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED
997                + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED);
998    }
999
1000    @Override
1001    public void onModeListClosed() {
1002        // Make sure the alpha on mode options ellipse is reset when mode drawer
1003        // is closed.
1004        mModeOptionsToggle.setAlpha(1f);
1005        mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
1006    }
1007
1008    /**
1009     * Called when the back key is pressed.
1010     *
1011     * @return Whether the UI responded to the key event.
1012     */
1013    public boolean onBackPressed() {
1014        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1015            return mFilmstripLayout.onBackPressed();
1016        } else {
1017            return mModeListView.onBackPressed();
1018        }
1019    }
1020
1021    /**
1022     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
1023     * listens to SurfaceTexture changes. In addition, listeners are set on
1024     * dependent app ui elements.
1025     *
1026     * @param previewStatusListener the listener that gets notified when SurfaceTexture
1027     *                              changes
1028     */
1029    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1030        mPreviewStatusListener = previewStatusListener;
1031        if (mPreviewStatusListener != null) {
1032            onPreviewListenerChanged();
1033        }
1034    }
1035
1036    /**
1037     * When the PreviewStatusListener changes, listeners need to be
1038     * set on the following app ui elements:
1039     * {@link com.android.camera.ui.PreviewOverlay},
1040     * {@link com.android.camera.ui.BottomBar},
1041     * {@link com.android.camera.ui.IndicatorIconController}.
1042     */
1043    private void onPreviewListenerChanged() {
1044        // Set a listener for recognizing preview gestures.
1045        GestureDetector.OnGestureListener gestureListener
1046            = mPreviewStatusListener.getGestureListener();
1047        if (gestureListener != null) {
1048            mPreviewOverlay.setGestureListener(gestureListener);
1049        }
1050        View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
1051        if (touchListener != null) {
1052            mPreviewOverlay.setTouchListener(touchListener);
1053        }
1054
1055        mTextureViewHelper.setAutoAdjustTransform(
1056                mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
1057    }
1058
1059    /**
1060     * This method should be called in onCameraOpened.  It defines CameraAppUI
1061     * specific changes that depend on the camera or camera settings.
1062     */
1063    public void onChangeCamera() {
1064        ModuleController moduleController = mController.getCurrentModuleController();
1065        applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec());
1066
1067        if (mIndicatorIconController != null) {
1068            // Sync the settings state with the indicator state.
1069            mIndicatorIconController.syncIndicators();
1070        }
1071    }
1072
1073    /**
1074     * Adds a listener to receive callbacks when preview area changes.
1075     */
1076    public void addPreviewAreaChangedListener(
1077            PreviewStatusListener.PreviewAreaChangedListener listener) {
1078        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
1079    }
1080
1081    /**
1082     * Removes a listener that receives callbacks when preview area changes.
1083     */
1084    public void removePreviewAreaChangedListener(
1085            PreviewStatusListener.PreviewAreaChangedListener listener) {
1086        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
1087    }
1088
1089    /**
1090     * This inflates generic_module layout, which contains all the shared views across
1091     * modules. Then each module inflates their own views in the given view group. For
1092     * now, this is called every time switching from a not-yet-refactored module to a
1093     * refactored module. In the future, this should only need to be done once per app
1094     * start.
1095     */
1096    public void prepareModuleUI() {
1097        mController.getSettingsManager().addListener(this);
1098        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
1099        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
1100        mTextureViewHelper = new TextureViewHelper(mTextureView, mCaptureLayoutHelper);
1101        mTextureViewHelper.setSurfaceTextureListener(this);
1102        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
1103
1104        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
1105        int unpressedColor = mController.getAndroidContext().getResources()
1106            .getColor(R.color.bottombar_unpressed);
1107        setBottomBarColor(unpressedColor);
1108        int pressedColor = mController.getAndroidContext().getResources()
1109            .getColor(R.color.bottombar_pressed);
1110        setBottomBarPressedColor(pressedColor);
1111        mBottomBar.setCaptureLayoutHelper(mCaptureLayoutHelper);
1112
1113        mModeOptionsOverlay
1114            = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
1115
1116        // Sets the visibility of the bottom bar and the mode options.
1117        resetBottomControls(mController.getCurrentModuleController(),
1118            mController.getCurrentModuleIndex());
1119        mModeOptionsOverlay.setCaptureLayoutHelper(mCaptureLayoutHelper);
1120
1121        mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1122        addShutterListener(mController.getCurrentModuleController());
1123        addShutterListener(mModeOptionsOverlay);
1124        addShutterListener(this);
1125
1126        mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
1127        mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
1128
1129        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
1130        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
1131        mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
1132
1133        mCaptureOverlay = (CaptureAnimationOverlay)
1134                mCameraRootView.findViewById(R.id.capture_overlay);
1135        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
1136        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
1137
1138        if (mIndicatorIconController == null) {
1139            mIndicatorIconController =
1140                new IndicatorIconController(mController, mAppRootView);
1141        }
1142
1143        mController.getButtonManager().load(mCameraRootView);
1144        mController.getButtonManager().setListener(mIndicatorIconController);
1145        mController.getSettingsManager().addListener(mIndicatorIconController);
1146
1147        mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1148        mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
1149        mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView
1150                .findViewById(R.id.tutorials_placeholder_wrapper);
1151        mIndicatorBottomBarWrapper = (BottomBarModeOptionsWrapper) mAppRootView
1152                .findViewById(R.id.indicator_bottombar_wrapper);
1153        mIndicatorBottomBarWrapper.setCaptureLayoutHelper(mCaptureLayoutHelper);
1154        mTextureViewHelper.addPreviewAreaSizeChangedListener(
1155                new PreviewStatusListener.PreviewAreaChangedListener() {
1156                    @Override
1157                    public void onPreviewAreaChanged(RectF previewArea) {
1158                        mPeekView.setTranslationX(previewArea.right - mAppRootView.getRight());
1159                    }
1160                });
1161
1162        mTextureViewHelper.addAspectRatioChangedListener(
1163                new PreviewStatusListener.PreviewAspectRatioChangedListener() {
1164                    @Override
1165                    public void onPreviewAspectRatioChanged(float aspectRatio) {
1166                        mModeOptionsOverlay.requestLayout();
1167                        mBottomBar.requestLayout();
1168                    }
1169                }
1170        );
1171    }
1172
1173    /**
1174     * Called indirectly from each module in their initialization to get a view group
1175     * to inflate the module specific views in.
1176     *
1177     * @return a view group for modules to attach views to
1178     */
1179    public FrameLayout getModuleRootView() {
1180        // TODO: Change it to mModuleUI when refactor is done
1181        return mCameraRootView;
1182    }
1183
1184    /**
1185     * Remove all the module specific views.
1186     */
1187    public void clearModuleUI() {
1188        if (mModuleUI != null) {
1189            mModuleUI.removeAllViews();
1190        }
1191        removeShutterListener(mController.getCurrentModuleController());
1192        mTutorialsPlaceHolderWrapper.removeAllViews();
1193        mTutorialsPlaceHolderWrapper.setVisibility(View.GONE);
1194
1195        setShutterButtonEnabled(true);
1196        mPreviewStatusListener = null;
1197        mPreviewOverlay.reset();
1198        mFocusOverlay.setVisibility(View.INVISIBLE);
1199    }
1200
1201    /**
1202     * Gets called when preview is ready to start. It sets up one shot preview callback
1203     * in order to receive a callback when the preview frame is available, so that
1204     * the preview cover can be hidden to reveal preview.
1205     *
1206     * An alternative for getting the timing to hide preview cover is through
1207     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1208     * which is less accurate but therefore is the fallback for modules that manage
1209     * their own preview callbacks (as setting one preview callback will override
1210     * any other installed preview callbacks), or use camera2 API.
1211     */
1212    public void onPreviewReadyToStart() {
1213        if (mModeCoverState == COVER_SHOWN) {
1214            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1215            mController.setupOneShotPreviewListener();
1216        }
1217    }
1218
1219    /**
1220     * Gets called when preview is started.
1221     */
1222    public void onPreviewStarted() {
1223        if (mModeCoverState == COVER_SHOWN) {
1224            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1225        }
1226        enableModeOptions();
1227    }
1228
1229    /**
1230     * Gets notified when next preview frame comes in.
1231     */
1232    public void onNewPreviewFrame() {
1233        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1234        hideModeCover();
1235        mModeCoverState = COVER_HIDDEN;
1236    }
1237
1238    /**
1239     * Set the mode options toggle clickable.
1240     */
1241    public void enableModeOptions() {
1242        /*
1243         * For modules using camera 1 api, this gets called in
1244         * onSurfaceTextureUpdated whenever the preview gets stopped and
1245         * started after each capture.  This also takes care of the
1246         * case where the mode options might be unclickable when we
1247         * switch modes
1248         *
1249         * For modules using camera 2 api, they're required to call this
1250         * method when a capture is "completed".  Unfortunately this differs
1251         * per module implementation.
1252         */
1253        mModeOptionsOverlay.setToggleClickable(true);
1254    }
1255
1256    @Override
1257    public void onShutterButtonClick() {
1258        /*
1259         * Set the mode options toggle unclickable, generally
1260         * throughout the app, whenever the shutter button is clicked.
1261         *
1262         * This could be done in the OnShutterButtonListener of the
1263         * ModeOptionsOverlay, but since it is very important that we
1264         * can clearly see when the toggle becomes clickable again,
1265         * keep all of that logic at this level.
1266         */
1267        mModeOptionsOverlay.setToggleClickable(false);
1268    }
1269
1270    @Override
1271    public void onShutterButtonFocus(boolean pressed) {
1272        // noop
1273    }
1274
1275    /**
1276     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1277     *
1278     * @param modeIndex mode index of the selected mode
1279     */
1280    @Override
1281    public void onModeSelected(int modeIndex) {
1282        mHideCoverRunnable = new Runnable() {
1283            @Override
1284            public void run() {
1285                mModeListView.startModeSelectionAnimation();
1286            }
1287        };
1288        mModeCoverState = COVER_SHOWN;
1289
1290        int lastIndex = mController.getCurrentModuleIndex();
1291        mController.onModeSelected(modeIndex);
1292        int currentIndex = mController.getCurrentModuleIndex();
1293
1294        if (lastIndex == currentIndex) {
1295            hideModeCover();
1296        }
1297    }
1298
1299    @Override
1300    public void onSettingsSelected() {
1301        mController.onSettingsSelected();
1302    }
1303
1304    @Override
1305    public int getCurrentModeIndex() {
1306        return mController.getCurrentModuleIndex();
1307    }
1308
1309    /********************** Capture animation **********************/
1310    /* TODO: This session is subject to UX changes. In addition to the generic
1311       flash animation and post capture animation, consider designating a parameter
1312       for specifying the type of animation, as well as an animation finished listener
1313       so that modules can have more knowledge of the status of the animation. */
1314
1315    /**
1316     * Starts the filmstrip peek animation.
1317     *
1318     * @param bitmap The bitmap to show.
1319     * @param strong Whether the animation shows more portion of the bitmap or
1320     *               not.
1321     */
1322    public void startPeekAnimation(Bitmap bitmap, boolean strong) {
1323        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1324            return;
1325        }
1326        mPeekView.startPeekAnimation(bitmap, strong);
1327    }
1328
1329    /**
1330     * Starts the pre-capture animation.
1331     */
1332    public void startPreCaptureAnimation() {
1333        mCaptureOverlay.startFlashAnimation();
1334    }
1335
1336    /**
1337     * Cancels the pre-capture animation.
1338     */
1339    public void cancelPreCaptureAnimation() {
1340        mAnimationManager.cancelAnimations();
1341    }
1342
1343    /**
1344     * Cancels the post-capture animation.
1345     */
1346    public void cancelPostCaptureAnimation() {
1347        mAnimationManager.cancelAnimations();
1348    }
1349
1350    public FilmstripContentPanel getFilmstripContentPanel() {
1351        return mFilmstripPanel;
1352    }
1353
1354    /**
1355     * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the
1356     * bottom of the filmstrip.
1357     */
1358    public BottomPanel getFilmstripBottomControls() {
1359        return mFilmstripBottomControls;
1360    }
1361
1362    /**
1363     * @param listener The listener for bottom controls.
1364     */
1365    public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) {
1366        mFilmstripBottomControls.setListener(listener);
1367    }
1368
1369    /***************************SurfaceTexture Api and Listener*********************************/
1370
1371    /**
1372     * Return the shared surface texture.
1373     */
1374    public SurfaceTexture getSurfaceTexture() {
1375        return mSurface;
1376    }
1377
1378    /**
1379     * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1380     */
1381    public int getSurfaceWidth() {
1382        return mSurfaceWidth;
1383    }
1384
1385    /**
1386     * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1387     */
1388    public int getSurfaceHeight() {
1389        return mSurfaceHeight;
1390    }
1391
1392    @Override
1393    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1394        mSurface = surface;
1395        mSurfaceWidth = width;
1396        mSurfaceHeight = height;
1397        Log.v(TAG, "SurfaceTexture is available");
1398        if (mPreviewStatusListener != null) {
1399            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1400        }
1401        enableModeOptions();
1402    }
1403
1404    @Override
1405    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1406        mSurface = surface;
1407        mSurfaceWidth = width;
1408        mSurfaceHeight = height;
1409        if (mPreviewStatusListener != null) {
1410            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1411        }
1412    }
1413
1414    @Override
1415    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1416        mSurface = null;
1417        Log.v(TAG, "SurfaceTexture is destroyed");
1418        if (mPreviewStatusListener != null) {
1419            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1420        }
1421        return false;
1422    }
1423
1424    @Override
1425    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1426        mSurface = surface;
1427        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1428            CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1429            hideModeCover();
1430            mModeCoverState = COVER_HIDDEN;
1431        }
1432        if (mPreviewStatusListener != null) {
1433            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1434        }
1435    }
1436
1437    /****************************Grid lines api ******************************/
1438
1439    /**
1440     * Show a set of evenly spaced lines over the preview.  The number
1441     * of lines horizontally and vertically is determined by
1442     * {@link com.android.camera.ui.GridLines}.
1443     */
1444    public void showGridLines() {
1445        if (mGridLines != null) {
1446            mGridLines.setVisibility(View.VISIBLE);
1447        }
1448    }
1449
1450    /**
1451     * Hide the set of evenly spaced grid lines overlaying the preview.
1452     */
1453    public void hideGridLines() {
1454        if (mGridLines != null) {
1455            mGridLines.setVisibility(View.INVISIBLE);
1456        }
1457    }
1458
1459    /**
1460     * Return a callback which shows or hide the preview grid lines
1461     * depending on whether the grid lines setting is set on.
1462     */
1463    public ButtonManager.ButtonCallback getGridLinesCallback() {
1464        return new ButtonManager.ButtonCallback() {
1465            @Override
1466            public void onStateChanged(int state) {
1467                if (mController.getSettingsManager().areGridLinesOn()) {
1468                    showGridLines();
1469                } else {
1470                    hideGridLines();
1471                }
1472            }
1473        };
1474    }
1475
1476    /***************************Mode options api *****************************/
1477
1478    /**
1479     * Set the mode options visible.
1480     */
1481    public void showModeOptions() {
1482        /* Make mode options clickable. */
1483        enableModeOptions();
1484        mModeOptionsOverlay.setVisibility(View.VISIBLE);
1485    }
1486
1487    /**
1488     * Set the mode options invisible.  This is necessary for modes
1489     * that don't show a bottom bar for the capture UI.
1490     */
1491    public void hideModeOptions() {
1492        mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1493    }
1494
1495    /****************************Bottom bar api ******************************/
1496
1497    /**
1498     * Sets up the bottom bar and mode options with the correct
1499     * shutter button and visibility based on the current module.
1500     */
1501    public void resetBottomControls(ModuleController module, int moduleIndex) {
1502        if (areBottomControlsUsed(module)) {
1503            setBottomBarShutterIcon(moduleIndex);
1504            mCaptureLayoutHelper.setShowBottomBar(true);
1505        } else {
1506            mCaptureLayoutHelper.setShowBottomBar(false);
1507        }
1508    }
1509
1510    /**
1511     * Show or hide the mode options and bottom bar, based on
1512     * whether the current module is using the bottom bar.  Returns
1513     * whether the mode options and bottom bar are used.
1514     */
1515    private boolean areBottomControlsUsed(ModuleController module) {
1516        if (module.isUsingBottomBar()) {
1517            showBottomBar();
1518            showModeOptions();
1519            return true;
1520        } else {
1521            hideBottomBar();
1522            hideModeOptions();
1523            return false;
1524        }
1525    }
1526
1527    /**
1528     * Set the bottom bar visible.
1529     */
1530    public void showBottomBar() {
1531        mBottomBar.setVisibility(View.VISIBLE);
1532    }
1533
1534    /**
1535     * Set the bottom bar invisible.
1536     */
1537    public void hideBottomBar() {
1538        mBottomBar.setVisibility(View.INVISIBLE);
1539    }
1540
1541    /**
1542     * Sets the color of the bottom bar.
1543     */
1544    public void setBottomBarColor(int colorId) {
1545        mBottomBar.setBackgroundColor(colorId);
1546    }
1547
1548    /**
1549     * Sets the pressed color of the bottom bar.
1550     */
1551    public void setBottomBarPressedColor(int colorId) {
1552        mBottomBar.setBackgroundPressedColor(colorId);
1553    }
1554
1555    /**
1556     * Sets the shutter button icon on the bottom bar, based on
1557     * the mode index.
1558     */
1559    public void setBottomBarShutterIcon(int modeIndex) {
1560        int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1561            mController.getAndroidContext());
1562        mBottomBar.setShutterButtonIcon(shutterIconId);
1563    }
1564
1565    public void animateBottomBarToVideoStop(int shutterIconId) {
1566        mBottomBar.animateToVideoStop(shutterIconId);
1567    }
1568
1569    public void animateBottomBarToFullSize(int shutterIconId) {
1570        mBottomBar.animateToFullSize(shutterIconId);
1571    }
1572
1573    public void setShutterButtonEnabled(boolean enabled) {
1574        mBottomBar.setShutterButtonEnabled(enabled);
1575    }
1576
1577    public boolean isShutterButtonEnabled() {
1578        return mBottomBar.isShutterButtonEnabled();
1579    }
1580
1581    public void setIndicatorBottomBarWrapperVisible(boolean visible) {
1582        mIndicatorBottomBarWrapper.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1583    }
1584
1585    /**
1586     * Set the visibility of the bottom bar.
1587     */
1588    // TODO: needed for when panorama is managed by the generic module ui.
1589    public void setBottomBarVisible(boolean visible) {
1590        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1591    }
1592
1593    /**
1594     * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button.
1595     */
1596    public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1597        mShutterButton.addOnShutterButtonListener(listener);
1598    }
1599
1600    /**
1601     * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
1602     */
1603    public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) {
1604        mShutterButton.removeOnShutterButtonListener(listener);
1605    }
1606
1607    /**
1608     * Performs a transition to the capture layout of the bottom bar.
1609     */
1610    public void transitionToCapture() {
1611        ModuleController moduleController = mController.getCurrentModuleController();
1612        applyModuleSpecs(moduleController.getHardwareSpec(),
1613            moduleController.getBottomBarSpec());
1614        mBottomBar.transitionToCapture();
1615    }
1616
1617    /**
1618     * Displays the Cancel button instead of the capture button.
1619     */
1620    public void transitionToCancel() {
1621        ModuleController moduleController = mController.getCurrentModuleController();
1622        applyModuleSpecs(moduleController.getHardwareSpec(),
1623                moduleController.getBottomBarSpec());
1624        mBottomBar.transitionToCancel();
1625    }
1626
1627    /**
1628     * Performs a transition to the global intent layout.
1629     */
1630    public void transitionToIntentCaptureLayout() {
1631        ModuleController moduleController = mController.getCurrentModuleController();
1632        applyModuleSpecs(moduleController.getHardwareSpec(),
1633            moduleController.getBottomBarSpec());
1634        mBottomBar.transitionToIntentCaptureLayout();
1635    }
1636
1637    /**
1638     * Performs a transition to the global intent review layout.
1639     */
1640    public void transitionToIntentReviewLayout() {
1641        ModuleController moduleController = mController.getCurrentModuleController();
1642        applyModuleSpecs(moduleController.getHardwareSpec(),
1643            moduleController.getBottomBarSpec());
1644        mBottomBar.transitionToIntentReviewLayout();
1645    }
1646
1647    @Override
1648    public void onSettingChanged(SettingsManager settingsManager, int id) {
1649        // Update the mode options based on the hardware spec,
1650        // when hdr changes to prevent flash from getting out of sync.
1651        if (id == SettingsManager.SETTING_CAMERA_HDR) {
1652            ModuleController moduleController = mController.getCurrentModuleController();
1653            applyModuleSpecs(moduleController.getHardwareSpec(),
1654                             moduleController.getBottomBarSpec());
1655        }
1656    }
1657
1658    /**
1659     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1660     * to the bottom bar mode options based on limitations from a
1661     * {@link com.android.camera.hardware.HardwareSpec}.
1662     *
1663     * Options not supported by the hardware are either hidden
1664     * or disabled, depending on the option.
1665     *
1666     * Otherwise, the option is fully enabled and clickable.
1667     */
1668    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1669           final BottomBarUISpec bottomBarSpec) {
1670        if (hardwareSpec == null || bottomBarSpec == null) {
1671            return;
1672        }
1673
1674        ButtonManager buttonManager = mController.getButtonManager();
1675        SettingsManager settingsManager = mController.getSettingsManager();
1676
1677        buttonManager.setToInitialState();
1678
1679        /** Standard mode options */
1680        if (hardwareSpec.isFrontCameraSupported()) {
1681            if (bottomBarSpec.enableCamera) {
1682                buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA,
1683                        bottomBarSpec.cameraCallback);
1684            } else {
1685                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1686            }
1687        } else {
1688            // Hide camera icon if front camera not available.
1689            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1690        }
1691
1692        boolean flashBackCamera = mController.getSettingsManager().getBoolean(
1693            SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA);
1694        if (bottomBarSpec.hideFlash || !flashBackCamera) {
1695            buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
1696        } else {
1697            if (hardwareSpec.isFlashSupported()) {
1698                if (bottomBarSpec.enableFlash) {
1699                    buttonManager.initializeButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1700                } else if (bottomBarSpec.enableTorchFlash) {
1701                    buttonManager.initializeButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1702                } else {
1703                    buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1704                }
1705            } else {
1706                // Disable flash icon if not supported by the hardware.
1707                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1708            }
1709        }
1710
1711        if (bottomBarSpec.hideHdr || mIsCaptureIntent) {
1712            // Force hide hdr or hdr plus icon.
1713            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1714        } else {
1715            if (hardwareSpec.isHdrPlusSupported()) {
1716                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1717                    buttonManager.initializeButton(ButtonManager.BUTTON_HDRPLUS,
1718                            bottomBarSpec.hdrCallback);
1719                } else {
1720                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1721                }
1722            } else if (hardwareSpec.isHdrSupported()) {
1723                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1724                    buttonManager.initializeButton(ButtonManager.BUTTON_HDR,
1725                            bottomBarSpec.hdrCallback);
1726                } else {
1727                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1728                }
1729            } else {
1730                // Hide hdr plus or hdr icon if neither are supported.
1731                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1732            }
1733        }
1734
1735        if (bottomBarSpec.hideGridLines) {
1736            // Force hide grid lines icon.
1737            buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
1738            hideGridLines();
1739        } else {
1740            if (bottomBarSpec.enableGridLines) {
1741                buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES,
1742                        bottomBarSpec.gridLinesCallback != null ?
1743                                bottomBarSpec.gridLinesCallback : getGridLinesCallback()
1744                );
1745            } else {
1746                buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
1747                hideGridLines();
1748            }
1749        }
1750
1751        if (bottomBarSpec.enablePanoOrientation
1752                && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
1753            buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback);
1754        }
1755
1756        boolean enableExposureCompensation = bottomBarSpec.enableExposureCompensation &&
1757            !(bottomBarSpec.minExposureCompensation == 0 && bottomBarSpec.maxExposureCompensation == 0) &&
1758            mController.getSettingsManager()
1759            .getBoolean(SettingsManager.SETTING_EXPOSURE_COMPENSATION_ENABLED);
1760        if (enableExposureCompensation) {
1761            buttonManager.initializePushButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION, null);
1762            buttonManager.setExposureCompensationParameters(
1763                bottomBarSpec.minExposureCompensation,
1764                bottomBarSpec.maxExposureCompensation,
1765                bottomBarSpec.exposureCompensationStep);
1766
1767            buttonManager.setExposureCompensationCallback(
1768                    bottomBarSpec.exposureCompensationSetCallback);
1769            buttonManager.updateExposureButtons();
1770        } else {
1771            buttonManager.hideButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION);
1772            buttonManager.setExposureCompensationCallback(null);
1773        }
1774
1775        /** Intent UI */
1776        if (bottomBarSpec.showCancel) {
1777            buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL,
1778                    bottomBarSpec.cancelCallback);
1779        }
1780        if (bottomBarSpec.showDone) {
1781            buttonManager.initializePushButton(ButtonManager.BUTTON_DONE,
1782                    bottomBarSpec.doneCallback);
1783        }
1784        if (bottomBarSpec.showRetake) {
1785            buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE,
1786                    bottomBarSpec.retakeCallback);
1787        }
1788        if (bottomBarSpec.showReview) {
1789            buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW,
1790                    bottomBarSpec.reviewCallback,
1791                    R.drawable.ic_play);
1792        }
1793    }
1794
1795    /**
1796     * Shows the given tutorial on the screen.
1797     */
1798    public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) {
1799        tutorial.show(mTutorialsPlaceHolderWrapper, inflater);
1800    }
1801
1802    /***************************Filmstrip api *****************************/
1803
1804    public void showFilmstrip() {
1805        mModeListView.onBackPressed();
1806        mFilmstripLayout.showFilmstrip();
1807    }
1808
1809    public void hideFilmstrip() {
1810        mFilmstripLayout.hideFilmstrip();
1811    }
1812}
1813