CameraAppUI.java revision a83ec8a3acb174e2688f0d31f2094e1aa72a06ee
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        // TODO: This can be removed once we come up with a new design for handling swipe
764        // on shutter button and mode options. (More details: b/13751653)
765        mAppRootView.setSwipeEnabled(enabled);
766    }
767
768    public void onDestroy() {
769        ((DisplayManager) mController.getAndroidContext()
770                .getSystemService(Context.DISPLAY_SERVICE))
771                .unregisterDisplayListener(mDisplayListener);
772    }
773
774    /**
775     * Initializes the display listener to listen to display changes such as
776     * 180-degree rotation change, which will not have an onConfigurationChanged
777     * callback.
778     */
779    private void initDisplayListener() {
780        if (ApiHelper.HAS_DISPLAY_LISTENER) {
781            mLastRotation = CameraUtil.getDisplayRotation(mController.getAndroidContext());
782
783            mDisplayListener = new DisplayManager.DisplayListener() {
784                @Override
785                public void onDisplayAdded(int arg0) {
786                    // Do nothing.
787                }
788
789                @Override
790                public void onDisplayChanged(int displayId) {
791                    int rotation = CameraUtil.getDisplayRotation(
792                            mController.getAndroidContext());
793                    if ((rotation - mLastRotation + 360) % 360 == 180
794                            && mPreviewStatusListener != null) {
795                        mPreviewStatusListener.onPreviewFlipped();
796                    }
797                    mLastRotation = rotation;
798                }
799
800                @Override
801                public void onDisplayRemoved(int arg0) {
802                    // Do nothing.
803                }
804            };
805
806            ((DisplayManager) mController.getAndroidContext()
807                    .getSystemService(Context.DISPLAY_SERVICE))
808                    .registerDisplayListener(mDisplayListener, null);
809        }
810    }
811
812    /**
813     * Redirects touch events to appropriate recipient views based on swipe direction.
814     * More specifically, swipe up and swipe down will be handled by the view that handles
815     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
816     * to mode list in order to bring up mode list.
817     */
818    private void onSwipeDetected(int swipeState) {
819        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
820            // TODO: Polish quick switch after this release.
821            // Quick switch between modes.
822            int currentModuleIndex = mController.getCurrentModuleIndex();
823            final int moduleToTransitionTo =
824                    mController.getQuickSwitchToModuleId(currentModuleIndex);
825            if (currentModuleIndex != moduleToTransitionTo) {
826                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
827
828                int shadeColorId = R.color.mode_cover_default_color;
829                int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
830                        mController.getAndroidContext());
831
832                AnimationFinishedListener listener = new AnimationFinishedListener() {
833                    @Override
834                    public void onAnimationFinished(boolean success) {
835                        if (success) {
836                            mHideCoverRunnable = new Runnable() {
837                                @Override
838                                public void run() {
839                                    mModeTransitionView.startPeepHoleAnimation();
840                                }
841                            };
842                            mModeCoverState = COVER_SHOWN;
843                            // Go to new module when the previous operation is successful.
844                            mController.onModeSelected(moduleToTransitionTo);
845                        }
846                    }
847                };
848                if (mSwipeState == SWIPE_UP) {
849                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
850                } else {
851                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
852                }
853            }
854        } else if (swipeState == SWIPE_LEFT) {
855            // Pass the touch sequence to filmstrip layout.
856            UsageStatistics.instance().changeScreen(
857                    eventprotos.NavigationChange.Mode.FILMSTRIP,
858                    eventprotos.CameraEvent.InteractionCause.SWIPE_LEFT);
859            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
860        } else if (swipeState == SWIPE_RIGHT) {
861            // Pass the touch to mode switcher
862            mAppRootView.redirectTouchEventsTo(mModeListView);
863        }
864    }
865
866    /**
867     * Gets called when activity resumes in preview.
868     */
869    public void resume() {
870        // Show mode theme cover until preview is ready
871        showModeCoverUntilPreviewReady();
872
873        // Hide action bar first since we are in full screen mode first, and
874        // switch the system UI to lights-out mode.
875        mFilmstripPanel.hide();
876    }
877
878    /**
879     * A cover view showing the mode theme color and mode icon will be visible on
880     * top of preview until preview is ready (i.e. camera preview is started and
881     * the first frame has been received).
882     */
883    private void showModeCoverUntilPreviewReady() {
884        int modeId = mController.getCurrentModuleIndex();
885        int colorId = R.color.mode_cover_default_color;;
886        int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext());
887        mModeTransitionView.setupModeCover(colorId, iconId);
888        mHideCoverRunnable = new Runnable() {
889            @Override
890            public void run() {
891                mModeTransitionView.hideModeCover(null);
892                showShimmyDelayed();
893            }
894        };
895        mModeCoverState = COVER_SHOWN;
896    }
897
898    private void showShimmyDelayed() {
899        if (!mIsCaptureIntent) {
900            // Show shimmy in SHIMMY_DELAY_MS
901            mModeListView.showModeSwitcherHint();
902        }
903    }
904
905    private void hideModeCover() {
906        if (mHideCoverRunnable != null) {
907            mAppRootView.post(mHideCoverRunnable);
908            mHideCoverRunnable = null;
909        }
910        mModeCoverState = COVER_HIDDEN;
911        if (mCoverHiddenTime < 0) {
912            mCoverHiddenTime = System.currentTimeMillis();
913        }
914    }
915
916    /**
917     * Call to stop the preview from being rendered.
918     */
919    public void pausePreviewRendering() {
920        mTextureView.setVisibility(View.INVISIBLE);
921    }
922
923    /**
924     * Call to begin rendering the preview again.
925     */
926    public void resumePreviewRendering() {
927        mTextureView.setVisibility(View.VISIBLE);
928    }
929
930    @Override
931    public void onOpenFullScreen() {
932        // Do nothing.
933    }
934
935    @Override
936    public void onModeListOpenProgress(float progress) {
937        progress = 1 - progress;
938        float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
939        mModeOptionsToggle.setAlpha(interpolatedProgress);
940        // Change shutter button alpha linearly based on the mode list open progress:
941        // set the alpha to disabled alpha when list is fully open, to enabled alpha
942        // when the list is fully closed.
943        mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED
944                + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED);
945    }
946
947    @Override
948    public void onModeListClosed() {
949        // Make sure the alpha on mode options ellipse is reset when mode drawer
950        // is closed.
951        mModeOptionsToggle.setAlpha(1f);
952        mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
953    }
954
955    /**
956     * Called when the back key is pressed.
957     *
958     * @return Whether the UI responded to the key event.
959     */
960    public boolean onBackPressed() {
961        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
962            return mFilmstripLayout.onBackPressed();
963        } else {
964            return mModeListView.onBackPressed();
965        }
966    }
967
968    /**
969     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
970     * listens to SurfaceTexture changes. In addition, listeners are set on
971     * dependent app ui elements.
972     *
973     * @param previewStatusListener the listener that gets notified when SurfaceTexture
974     *                              changes
975     */
976    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
977        mPreviewStatusListener = previewStatusListener;
978        if (mPreviewStatusListener != null) {
979            onPreviewListenerChanged();
980        }
981    }
982
983    /**
984     * When the PreviewStatusListener changes, listeners need to be
985     * set on the following app ui elements:
986     * {@link com.android.camera.ui.PreviewOverlay},
987     * {@link com.android.camera.ui.BottomBar},
988     * {@link com.android.camera.ui.IndicatorIconController}.
989     */
990    private void onPreviewListenerChanged() {
991        // Set a listener for recognizing preview gestures.
992        GestureDetector.OnGestureListener gestureListener
993            = mPreviewStatusListener.getGestureListener();
994        if (gestureListener != null) {
995            mPreviewOverlay.setGestureListener(gestureListener);
996        }
997        View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
998        if (touchListener != null) {
999            mPreviewOverlay.setTouchListener(touchListener);
1000        }
1001
1002        mTextureViewHelper.setAutoAdjustTransform(
1003                mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
1004    }
1005
1006    /**
1007     * This method should be called in onCameraOpened.  It defines CameraAppUI
1008     * specific changes that depend on the camera or camera settings.
1009     */
1010    public void onChangeCamera() {
1011        ModuleController moduleController = mController.getCurrentModuleController();
1012        applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec());
1013
1014        if (mIndicatorIconController != null) {
1015            // Sync the settings state with the indicator state.
1016            mIndicatorIconController.syncIndicators();
1017        }
1018    }
1019
1020    /**
1021     * Adds a listener to receive callbacks when preview area changes.
1022     */
1023    public void addPreviewAreaChangedListener(
1024            PreviewStatusListener.PreviewAreaChangedListener listener) {
1025        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
1026    }
1027
1028    /**
1029     * Removes a listener that receives callbacks when preview area changes.
1030     */
1031    public void removePreviewAreaChangedListener(
1032            PreviewStatusListener.PreviewAreaChangedListener listener) {
1033        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
1034    }
1035
1036    /**
1037     * This inflates generic_module layout, which contains all the shared views across
1038     * modules. Then each module inflates their own views in the given view group. For
1039     * now, this is called every time switching from a not-yet-refactored module to a
1040     * refactored module. In the future, this should only need to be done once per app
1041     * start.
1042     */
1043    public void prepareModuleUI() {
1044        mCameraRootView.removeAllViews();
1045        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
1046                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1047        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
1048
1049        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
1050        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
1051        mTextureViewHelper = new TextureViewHelper(mTextureView);
1052        mTextureViewHelper.setSurfaceTextureListener(this);
1053        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
1054
1055        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
1056        int unpressedColor = mController.getAndroidContext().getResources()
1057            .getColor(R.color.bottombar_unpressed);
1058        setBottomBarColor(unpressedColor);
1059        int pressedColor = mController.getAndroidContext().getResources()
1060            .getColor(R.color.bottombar_pressed);
1061        setBottomBarPressedColor(pressedColor);
1062
1063        mModeOptionsOverlay
1064            = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
1065        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeOptionsOverlay);
1066
1067        // Sets the visibility of the bottom bar and the mode options.
1068        resetBottomControls(mController.getCurrentModuleController(),
1069            mController.getCurrentModuleIndex());
1070
1071        mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1072        addShutterListener(mController.getCurrentModuleController());
1073        addShutterListener(mModeOptionsOverlay);
1074
1075        mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
1076        mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
1077
1078        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
1079        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
1080        mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
1081
1082        mCaptureOverlay = (CaptureAnimationOverlay)
1083                mCameraRootView.findViewById(R.id.capture_overlay);
1084        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
1085        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
1086        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
1087
1088        if (mIndicatorIconController == null) {
1089            mIndicatorIconController =
1090                new IndicatorIconController(mController, mAppRootView);
1091        }
1092
1093        mController.getButtonManager().load(mCameraRootView);
1094        mController.getButtonManager().setListener(mIndicatorIconController);
1095        mController.getSettingsManager().addListener(mIndicatorIconController);
1096
1097        mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1098        mBottomBar.addOnLayoutChangeListener(mBottomBarLayoutChangeListener);
1099        mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
1100        mTutorialsPlaceholder = (FrameLayout) mCameraRootView
1101                .findViewById(R.id.tutorials_placeholder);
1102        mIndicatorBottomBarWrapper = mAppRootView
1103                .findViewById(R.id.indicator_bottombar_wrapper);
1104
1105        mTextureViewHelper.addPreviewAreaSizeChangedListener(
1106                new PreviewStatusListener.PreviewAreaChangedListener() {
1107                    @Override
1108                    public void onPreviewAreaChanged(RectF previewArea) {
1109                        if (mPreviewStatusListener != null &&
1110                                mPreviewStatusListener.shouldAutoAdjustBottomBar()) {
1111                            mBottomBar.onPreviewAreaChanged(previewArea);
1112                        } else {
1113                            mPeekView.setTranslationX(previewArea.right - mAppRootView.getRight());
1114                        }
1115                    }
1116                });
1117
1118        mBottomBar.setAdjustPreviewAreaListener(new BottomBar.AdjustPreviewAreaListener() {
1119            @Override
1120            public void fitAndCenterPreviewAreaInRect(RectF rect) {
1121                mPeekView.setTranslationX(0f);
1122                if (mPreviewStatusListener != null &&
1123                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1124                    mTextureViewHelper.centerPreviewInRect(rect);
1125                }
1126            }
1127
1128            @Override
1129            public void fitAndAlignBottomInRect(RectF rect) {
1130                mPeekView.setTranslationX(0f);
1131                if (mPreviewStatusListener != null &&
1132                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1133                    mTextureViewHelper.alignBottomInRect(rect);
1134                }
1135            }
1136
1137            @Override
1138            public void fitAndAlignRightInRect(RectF rect) {
1139                mPeekView.setTranslationX(rect.right - mAppRootView.getRight());
1140                if (mPreviewStatusListener != null &&
1141                        mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout()) {
1142                    mTextureViewHelper.alignRightInRect(rect);
1143                }
1144            }
1145        });
1146    }
1147
1148    /**
1149     * Called indirectly from each module in their initialization to get a view group
1150     * to inflate the module specific views in.
1151     *
1152     * @return a view group for modules to attach views to
1153     */
1154    public FrameLayout getModuleRootView() {
1155        // TODO: Change it to mModuleUI when refactor is done
1156        return mCameraRootView;
1157    }
1158
1159    /**
1160     * Remove all the module specific views.
1161     */
1162    public void clearModuleUI() {
1163        if (mModuleUI != null) {
1164            mModuleUI.removeAllViews();
1165        }
1166        removeShutterListener(mController.getCurrentModuleController());
1167        mTutorialsPlaceholder.removeAllViews();
1168
1169        mPreviewStatusListener = null;
1170        mPreviewOverlay.reset();
1171        mFocusOverlay.setVisibility(View.INVISIBLE);
1172    }
1173
1174    /**
1175     * Gets called when preview is ready to start. It sets up one shot preview callback
1176     * in order to receive a callback when the preview frame is available, so that
1177     * the preview cover can be hidden to reveal preview.
1178     *
1179     * An alternative for getting the timing to hide preview cover is through
1180     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1181     * which is less accurate but therefore is the fallback for modules that manage
1182     * their own preview callbacks (as setting one preview callback will override
1183     * any other installed preview callbacks), or use camera2 API.
1184     */
1185    public void onPreviewReadyToStart() {
1186        if (mModeCoverState == COVER_SHOWN) {
1187            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1188            mController.setupOneShotPreviewListener();
1189        }
1190    }
1191
1192    /**
1193     * Gets called when preview is started.
1194     */
1195    public void onPreviewStarted() {
1196        if (mModeCoverState == COVER_SHOWN) {
1197            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1198        }
1199    }
1200
1201    /**
1202     * Gets notified when next preview frame comes in.
1203     */
1204    public void onNewPreviewFrame() {
1205        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1206        hideModeCover();
1207        mModeCoverState = COVER_HIDDEN;
1208    }
1209
1210    /**
1211     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1212     *
1213     * @param modeIndex mode index of the selected mode
1214     */
1215    @Override
1216    public void onModeSelected(int modeIndex) {
1217        mHideCoverRunnable = new Runnable() {
1218            @Override
1219            public void run() {
1220                mModeListView.startModeSelectionAnimation();
1221            }
1222        };
1223        mModeCoverState = COVER_SHOWN;
1224
1225        int lastIndex = mController.getCurrentModuleIndex();
1226        mController.onModeSelected(modeIndex);
1227        int currentIndex = mController.getCurrentModuleIndex();
1228
1229        if (lastIndex == currentIndex) {
1230            hideModeCover();
1231        }
1232    }
1233
1234    @Override
1235    public void onSettingsSelected() {
1236        mController.onSettingsSelected();
1237    }
1238
1239    @Override
1240    public int getCurrentModeIndex() {
1241        return mController.getCurrentModuleIndex();
1242    }
1243
1244    /********************** Capture animation **********************/
1245    /* TODO: This session is subject to UX changes. In addition to the generic
1246       flash animation and post capture animation, consider designating a parameter
1247       for specifying the type of animation, as well as an animation finished listener
1248       so that modules can have more knowledge of the status of the animation. */
1249
1250    /**
1251     * Starts the filmstrip peek animation.
1252     *
1253     * @param bitmap The bitmap to show.
1254     * @param strong Whether the animation shows more portion of the bitmap or
1255     *               not.
1256     */
1257    public void startPeekAnimation(Bitmap bitmap, boolean strong) {
1258        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1259            return;
1260        }
1261        mPeekView.startPeekAnimation(bitmap, strong);
1262    }
1263
1264    /**
1265     * Starts the pre-capture animation.
1266     */
1267    public void startPreCaptureAnimation() {
1268        mCaptureOverlay.startFlashAnimation();
1269    }
1270
1271    /**
1272     * Cancels the pre-capture animation.
1273     */
1274    public void cancelPreCaptureAnimation() {
1275        mAnimationManager.cancelAnimations();
1276    }
1277
1278    /**
1279     * Cancels the post-capture animation.
1280     */
1281    public void cancelPostCaptureAnimation() {
1282        mAnimationManager.cancelAnimations();
1283    }
1284
1285    public FilmstripContentPanel getFilmstripContentPanel() {
1286        return mFilmstripPanel;
1287    }
1288
1289    /**
1290     * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the
1291     * bottom of the filmstrip.
1292     */
1293    public BottomPanel getFilmstripBottomControls() {
1294        return mFilmstripBottomControls;
1295    }
1296
1297    /**
1298     * @param listener The listener for bottom controls.
1299     */
1300    public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) {
1301        mFilmstripBottomControls.setListener(listener);
1302    }
1303
1304    /***************************SurfaceTexture Api and Listener*********************************/
1305
1306    /**
1307     * Return the shared surface texture.
1308     */
1309    public SurfaceTexture getSurfaceTexture() {
1310        return mSurface;
1311    }
1312
1313    /**
1314     * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1315     */
1316    public int getSurfaceWidth() {
1317        return mSurfaceWidth;
1318    }
1319
1320    /**
1321     * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1322     */
1323    public int getSurfaceHeight() {
1324        return mSurfaceHeight;
1325    }
1326
1327    @Override
1328    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1329        mSurface = surface;
1330        mSurfaceWidth = width;
1331        mSurfaceHeight = height;
1332        Log.v(TAG, "SurfaceTexture is available");
1333        if (mPreviewStatusListener != null) {
1334            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1335        }
1336    }
1337
1338    @Override
1339    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1340        mSurface = surface;
1341        mSurfaceWidth = width;
1342        mSurfaceHeight = height;
1343        if (mPreviewStatusListener != null) {
1344            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1345        }
1346    }
1347
1348    @Override
1349    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1350        mSurface = null;
1351        Log.v(TAG, "SurfaceTexture is destroyed");
1352        if (mPreviewStatusListener != null) {
1353            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1354        }
1355        return false;
1356    }
1357
1358    @Override
1359    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1360        mSurface = surface;
1361        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1362            CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1363            hideModeCover();
1364            mModeCoverState = COVER_HIDDEN;
1365        }
1366        if (mPreviewStatusListener != null) {
1367            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1368        }
1369    }
1370
1371    /****************************Grid lines api ******************************/
1372
1373    /**
1374     * Show a set of evenly spaced lines over the preview.  The number
1375     * of lines horizontally and vertically is determined by
1376     * {@link com.android.camera.ui.GridLines}.
1377     */
1378    public void showGridLines() {
1379        if (mGridLines != null) {
1380            mGridLines.setVisibility(View.VISIBLE);
1381        }
1382    }
1383
1384    /**
1385     * Hide the set of evenly spaced grid lines overlaying the preview.
1386     */
1387    public void hideGridLines() {
1388        if (mGridLines != null) {
1389            mGridLines.setVisibility(View.INVISIBLE);
1390        }
1391    }
1392
1393    /**
1394     * Return a callback which shows or hide the preview grid lines
1395     * depending on whether the grid lines setting is set on.
1396     */
1397    public ButtonManager.ButtonCallback getGridLinesCallback() {
1398        return new ButtonManager.ButtonCallback() {
1399            @Override
1400            public void onStateChanged(int state) {
1401                if (mController.getSettingsManager().areGridLinesOn()) {
1402                    showGridLines();
1403                } else {
1404                    hideGridLines();
1405                }
1406            }
1407        };
1408    }
1409
1410    /***************************Mode options api *****************************/
1411
1412    /**
1413     * Set the mode options visible.
1414     */
1415    public void showModeOptions() {
1416        mModeOptionsOverlay.setVisibility(View.VISIBLE);
1417    }
1418
1419    /**
1420     * Set the mode options invisible.  This is necessary for modes
1421     * that don't show a bottom bar for the capture UI.
1422     */
1423    public void hideModeOptions() {
1424        mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1425    }
1426
1427    /****************************Bottom bar api ******************************/
1428
1429    /**
1430     * Sets up the bottom bar and mode options with the correct
1431     * shutter button and visibility based on the current module.
1432     */
1433    public void resetBottomControls(ModuleController module, int moduleIndex) {
1434        if (areBottomControlsUsed(module)) {
1435            setBottomBarShutterIcon(moduleIndex);
1436        }
1437    }
1438
1439    /**
1440     * Show or hide the mode options and bottom bar, based on
1441     * whether the current module is using the bottom bar.  Returns
1442     * whether the mode options and bottom bar are used.
1443     */
1444    private boolean areBottomControlsUsed(ModuleController module) {
1445        if (module.isUsingBottomBar()) {
1446            showBottomBar();
1447            showModeOptions();
1448            return true;
1449        } else {
1450            hideBottomBar();
1451            hideModeOptions();
1452            return false;
1453        }
1454    }
1455
1456    /**
1457     * Set the bottom bar visible.
1458     */
1459    public void showBottomBar() {
1460        mBottomBar.setVisibility(View.VISIBLE);
1461    }
1462
1463    /**
1464     * Set the bottom bar invisible.
1465     */
1466    public void hideBottomBar() {
1467        mBottomBar.setVisibility(View.INVISIBLE);
1468    }
1469
1470    /**
1471     * Sets the color of the bottom bar.
1472     */
1473    public void setBottomBarColor(int colorId) {
1474        mBottomBar.setBackgroundColor(colorId);
1475    }
1476
1477    /**
1478     * Sets the pressed color of the bottom bar.
1479     */
1480    public void setBottomBarPressedColor(int colorId) {
1481        mBottomBar.setBackgroundPressedColor(colorId);
1482    }
1483
1484    /**
1485     * Sets the shutter button icon on the bottom bar, based on
1486     * the mode index.
1487     */
1488    public void setBottomBarShutterIcon(int modeIndex) {
1489        int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1490            mController.getAndroidContext());
1491        mBottomBar.setShutterButtonIcon(shutterIconId);
1492    }
1493
1494    public void animateBottomBarToVideoStop(int shutterIconId) {
1495        mBottomBar.animateToVideoStop(shutterIconId);
1496    }
1497
1498    public void animateBottomBarToFullSize(int shutterIconId) {
1499        mBottomBar.animateToFullSize(shutterIconId);
1500    }
1501
1502    public void setCaptureButtonEnabled(boolean enabled) {
1503        mBottomBar.setCaptureButtonEnabled(enabled);
1504    }
1505
1506    public void setIndicatorBottomBarWrapperVisible(boolean visible) {
1507        mIndicatorBottomBarWrapper.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1508    }
1509
1510    /**
1511     * Set the visibility of the bottom bar.
1512     */
1513    // TODO: needed for when panorama is managed by the generic module ui.
1514    public void setBottomBarVisible(boolean visible) {
1515        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1516    }
1517
1518    /**
1519     * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button.
1520     */
1521    public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1522        mShutterButton.addOnShutterButtonListener(listener);
1523    }
1524
1525    /**
1526     * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
1527     */
1528    public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) {
1529        mShutterButton.removeOnShutterButtonListener(listener);
1530    }
1531
1532    /**
1533     * Performs a transition to the capture layout of the bottom bar.
1534     */
1535    public void transitionToCapture() {
1536        ModuleController moduleController = mController.getCurrentModuleController();
1537        applyModuleSpecs(moduleController.getHardwareSpec(),
1538            moduleController.getBottomBarSpec());
1539        mBottomBar.transitionToCapture();
1540    }
1541
1542    /**
1543     * Displays the Cancel button instead of the capture button.
1544     */
1545    public void transitionToCancel() {
1546        ModuleController moduleController = mController.getCurrentModuleController();
1547        applyModuleSpecs(moduleController.getHardwareSpec(),
1548                moduleController.getBottomBarSpec());
1549        mBottomBar.transitionToCancel();
1550    }
1551
1552    /**
1553     * Performs a transition to the global intent layout.
1554     */
1555    public void transitionToIntentCaptureLayout() {
1556        ModuleController moduleController = mController.getCurrentModuleController();
1557        applyModuleSpecs(moduleController.getHardwareSpec(),
1558            moduleController.getBottomBarSpec());
1559        mBottomBar.transitionToIntentCaptureLayout();
1560    }
1561
1562    /**
1563     * Performs a transition to the global intent review layout.
1564     */
1565    public void transitionToIntentReviewLayout() {
1566        ModuleController moduleController = mController.getCurrentModuleController();
1567        applyModuleSpecs(moduleController.getHardwareSpec(),
1568            moduleController.getBottomBarSpec());
1569        mBottomBar.transitionToIntentReviewLayout();
1570    }
1571
1572    /**
1573     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1574     * to the bottom bar mode options based on limitations from a
1575     * {@link com.android.camera.hardware.HardwareSpec}.
1576     *
1577     * Options not supported by the hardware are either hidden
1578     * or disabled, depending on the option.
1579     *
1580     * Otherwise, the option is fully enabled and clickable.
1581     */
1582    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1583           final BottomBarUISpec bottomBarSpec) {
1584        if (hardwareSpec == null || bottomBarSpec == null) {
1585            return;
1586        }
1587
1588        ButtonManager buttonManager = mController.getButtonManager();
1589        SettingsManager settingsManager = mController.getSettingsManager();
1590
1591        /** Standard mode options */
1592        if (hardwareSpec.isFrontCameraSupported()) {
1593            if (bottomBarSpec.enableCamera) {
1594                buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA,
1595                        bottomBarSpec.cameraCallback);
1596            } else {
1597                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1598            }
1599        } else {
1600            // Hide camera icon if front camera not available.
1601            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1602        }
1603
1604        boolean flashBackCamera = mController.getSettingsManager().getBoolean(
1605            SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA);
1606        if (bottomBarSpec.hideFlash || !flashBackCamera) {
1607            buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
1608        } else {
1609            if (hardwareSpec.isFlashSupported()) {
1610                if (bottomBarSpec.enableFlash) {
1611                    buttonManager.initializeButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1612                } else if (bottomBarSpec.enableTorchFlash) {
1613                    buttonManager.initializeButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1614                } else {
1615                    buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1616                }
1617            } else {
1618                // Disable flash icon if not supported by the hardware.
1619                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1620            }
1621        }
1622
1623        if (bottomBarSpec.hideHdr || mIsCaptureIntent) {
1624            // Force hide hdr or hdr plus icon.
1625            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1626        } else {
1627            if (hardwareSpec.isHdrPlusSupported()) {
1628                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1629                    buttonManager.initializeButton(ButtonManager.BUTTON_HDRPLUS,
1630                            bottomBarSpec.hdrCallback);
1631                } else {
1632                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1633                }
1634            } else if (hardwareSpec.isHdrSupported()) {
1635                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1636                    buttonManager.initializeButton(ButtonManager.BUTTON_HDR,
1637                            bottomBarSpec.hdrCallback);
1638                } else {
1639                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1640                }
1641            } else {
1642                // Hide hdr plus or hdr icon if neither are supported.
1643                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1644            }
1645        }
1646
1647        if (bottomBarSpec.hideGridLines) {
1648            // Force hide grid lines icon.
1649            buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
1650            hideGridLines();
1651        } else {
1652            if (bottomBarSpec.enableGridLines) {
1653                buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES,
1654                        bottomBarSpec.gridLinesCallback != null ?
1655                                bottomBarSpec.gridLinesCallback : getGridLinesCallback()
1656                );
1657            } else {
1658                buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
1659                hideGridLines();
1660            }
1661        }
1662
1663        if (bottomBarSpec.enablePanoOrientation
1664                && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
1665            buttonManager.initializeButton(ButtonManager.BUTTON_PANO_ORIENTATION,
1666                    bottomBarSpec.panoOrientationCallback);
1667        } else {
1668            buttonManager.hideButton(ButtonManager.BUTTON_PANO_ORIENTATION);
1669        }
1670
1671        /** Intent UI */
1672        if (bottomBarSpec.showCancel) {
1673            buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL,
1674                    bottomBarSpec.cancelCallback);
1675        }
1676        if (bottomBarSpec.showDone) {
1677            buttonManager.initializePushButton(ButtonManager.BUTTON_DONE,
1678                    bottomBarSpec.doneCallback);
1679        }
1680        if (bottomBarSpec.showRetake) {
1681            buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE,
1682                    bottomBarSpec.retakeCallback);
1683        }
1684        if (bottomBarSpec.showReview) {
1685            buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW,
1686                    bottomBarSpec.reviewCallback,
1687                    R.drawable.ic_play);
1688        }
1689    }
1690
1691    /**
1692     * Shows the given tutorial on the screen.
1693     */
1694    public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) {
1695        tutorial.show(mTutorialsPlaceholder, inflater);
1696    }
1697
1698    /***************************Filmstrip api *****************************/
1699
1700    public void showFilmstrip() {
1701        mFilmstripLayout.showFilmstrip();
1702    }
1703
1704    public void hideFilmstrip() {
1705        mFilmstripLayout.hideFilmstrip();
1706    }
1707}
1708