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