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