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