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