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