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