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