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