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