CameraAppUI.java revision 426901fb6adf728742ad2841ac595bc1414f3568
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.app.Activity;
20import android.content.Context;
21import android.graphics.Matrix;
22import android.graphics.SurfaceTexture;
23import android.hardware.display.DisplayManager;
24import android.util.Log;
25import android.view.GestureDetector;
26import android.view.LayoutInflater;
27import android.view.MotionEvent;
28import android.view.TextureView;
29import android.view.View;
30import android.view.ViewConfiguration;
31import android.view.ViewGroup;
32import android.widget.FrameLayout;
33
34import com.android.camera.AnimationManager;
35import com.android.camera.ButtonManager;
36import com.android.camera.ShutterButton;
37import com.android.camera.TextureViewHelper;
38import com.android.camera.filmstrip.FilmstripContentPanel;
39import com.android.camera.hardware.HardwareSpec;
40import com.android.camera.module.ModuleController;
41import com.android.camera.settings.SettingsManager;
42import com.android.camera.ui.BottomBar;
43import com.android.camera.ui.CaptureAnimationOverlay;
44import com.android.camera.ui.MainActivityLayout;
45import com.android.camera.ui.ModeListView;
46import com.android.camera.ui.ModeTransitionView;
47import com.android.camera.ui.PreviewOverlay;
48import com.android.camera.ui.PreviewStatusListener;
49import com.android.camera.util.ApiHelper;
50import com.android.camera.util.CameraUtil;
51import com.android.camera.util.PhotoSphereHelper;
52import com.android.camera.util.UsageStatistics;
53import com.android.camera.widget.FilmstripLayout;
54import com.android.camera.widget.IndicatorIconController;
55import com.android.camera.widget.IndicatorOverlay;
56import com.android.camera2.R;
57
58import com.google.common.logging.eventprotos;
59
60/**
61 * CameraAppUI centralizes control of views shared across modules. Whereas module
62 * specific views will be handled in each Module UI. For example, we can now
63 * bring the flash animation and capture animation up from each module to app
64 * level, as these animations are largely the same for all modules.
65 *
66 * This class also serves to disambiguate touch events. It recognizes all the
67 * swipe gestures that happen on the preview by attaching a touch listener to
68 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
69 * of how swipe from each direction should be handled, it can then redirect these
70 * events to appropriate recipient views.
71 */
72public class CameraAppUI implements ModeListView.ModeSwitchListener,
73        TextureView.SurfaceTextureListener, ModeListView.ModeListOpenListener {
74
75    /**
76     * The bottom controls on the filmstrip.
77     */
78    public static interface BottomControls {
79        /** Values for the view state of the button. */
80        public final int VIEWER_NONE = 0;
81        public final int VIEWER_PHOTO_SPHERE = 1;
82        public final int VIEWER_REFOCUS = 2;
83        public final int VIEWER_OTHER = 3;
84
85        /**
86         * Sets a new or replaces an existing listener for bottom control events.
87         */
88        void setListener(Listener listener);
89
90        /**
91         * Set if the bottom controls are visible.
92         * @param visible {@code true} if visible.
93         */
94        void setVisible(boolean visible);
95
96        /**
97         * @param visible Whether the button is visible.
98         */
99        void setEditButtonVisibility(boolean visible);
100
101        /**
102         * @param enabled Whether the button is enabled.
103         */
104        void setEditEnabled(boolean enabled);
105
106        /**
107         * Sets the visibility of the view-photosphere button.
108         *
109         * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE},
110         *            {@link #VIEWER_REFOCUS}.
111         */
112        void setViewerButtonVisibility(int state);
113
114        /**
115         * @param enabled Whether the button is enabled.
116         */
117        void setViewEnabled(boolean enabled);
118
119        /**
120         * @param visible Whether the button is visible.
121         */
122        void setTinyPlanetButtonVisibility(boolean visible);
123
124        /**
125         * @param enabled Whether the button is enabled.
126         */
127        void setTinyPlanetEnabled(boolean enabled);
128
129        /**
130         * @param visible Whether the button is visible.
131         */
132        void setDeleteButtonVisibility(boolean visible);
133
134        /**
135         * @param enabled Whether the button is enabled.
136         */
137        void setDeleteEnabled(boolean enabled);
138
139        /**
140         * @param visible Whether the button is visible.
141         */
142        void setShareButtonVisibility(boolean visible);
143
144        /**
145         * @param enabled Whether the button is enabled.
146         */
147        void setShareEnabled(boolean enabled);
148
149        /**
150         * Classes implementing this interface can listen for events on the bottom
151         * controls.
152         */
153        public static interface Listener {
154            /**
155             * Called when the user pressed the "view" button to e.g. view a photo
156             * sphere or RGBZ image.
157             */
158            public void onExternalViewer();
159
160            /**
161             * Called when the "edit" button is pressed.
162             */
163            public void onEdit();
164
165            /**
166             * Called when the "tiny planet" button is pressed.
167             */
168            public void onTinyPlanet();
169
170            /**
171             * Called when the "delete" button is pressed.
172             */
173            public void onDelete();
174
175            /**
176             * Called when the "share" button is pressed.
177             */
178            public void onShare();
179        }
180    }
181
182    /**
183     * BottomBarUISpec provides a structure for modules
184     * to specify their ideal bottom bar mode options layout.
185     *
186     * Once constructed by a module, this class should be
187     * treated as read only.
188     *
189     * The application then edits this spec according to
190     * hardware limitations and displays the final bottom
191     * bar ui.
192     */
193    public static class BottomBarUISpec {
194        /** Mode options UI */
195
196        /**
197         * Set true if the camera option should be enabled.
198         * If not set or false, and multiple cameras are supported,
199         * the camera option will be disabled.
200         *
201         * If multiple cameras are not supported, this preference
202         * is ignored and the camera option will not be visible.
203         */
204        public boolean enableCamera;
205
206        /**
207         * Set true if the photo flash option should be enabled.
208         * If not set or false, the photo flash option will be
209         * disabled.
210         *
211         * If the hardware does not support multiple flash values,
212         * this preference is ignored and the flash option will
213         * be disabled.  It will not be made invisible in order to
214         * preserve a consistent experience across devices and between
215         * front and back cameras.
216         */
217        public boolean enableFlash;
218
219        /**
220         * Set true if the video flash option should be enabled.
221         * Same disable rules apply as the photo flash option.
222         */
223        public boolean enableTorchFlash;
224
225        /**
226         * Set true if the hdr/hdr+ option should be enabled.
227         * If not set or false, the hdr/hdr+ option will be disabled.
228         *
229         * Hdr or hdr+ will be chosen based on hardware limitations,
230         * with hdr+ prefered.
231         *
232         * If hardware supports neither hdr nor hdr+, then the hdr/hdr+
233         * will not be visible.
234         */
235        public boolean enableHdr;
236
237        /**
238         * Set true if hdr/hdr+ should not be visible, regardless of
239         * hardware limitations.
240         */
241        public boolean hideHdr;
242
243        /**
244         * Set true if the refocus option should be enabled.
245         * If not set or false, the refocus option will be disabled.
246         *
247         * This option is not constrained by hardware limitations.
248         */
249        public boolean enableRefocus;
250
251        /**
252         * Set true if refocus should not be visible.
253         */
254        public boolean hideRefocus;
255
256        /**
257         * Set true if the panorama horizontal option should be visible.
258         *
259         * This option is not constrained by hardware limitations.
260         */
261        public boolean enablePanoHorizontal;
262
263        /**
264         * Set true if the panorama vertical option should be visible.
265         *
266         * This option is not constrained by hardware limitations.
267         */
268        public boolean enablePanoVertical;
269
270        /** Intent UI */
271
272        /**
273         * Set true if the intent ui cancel option should be visible.
274         */
275        public boolean showCancel;
276        /**
277         * Set true if the intent ui done option should be visible.
278         */
279        public boolean showDone;
280        /**
281         * Set true if the intent ui retake option should be visible.
282         */
283        public boolean showRetake;
284        /**
285         * Set true if the intent ui review option should be visible.
286         */
287        public boolean showReview;
288
289        /** Mode options callbacks */
290
291        /**
292         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
293         * that will be executed when the camera option is pressed. This
294         * callback can be null.
295         */
296        public ButtonManager.ButtonCallback cameraCallback;
297
298        /**
299         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
300         * that will be executed when the flash option is pressed. This
301         * callback can be null.
302         */
303        public ButtonManager.ButtonCallback flashCallback;
304
305        /**
306         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
307         * that will be executed when the hdr/hdr+ option is pressed. This
308         * callback can be null.
309         */
310        public ButtonManager.ButtonCallback hdrCallback;
311
312        /**
313         * A {@link android.com.android.camera.ButtonManager.ButtonCallback}
314         * that will be executed when the refocus option is pressed. This
315         * callback can be null.
316         */
317        public ButtonManager.ButtonCallback refocusCallback;
318
319        /**
320         * A {@link android.view.View.OnClickListener} that will execute
321         * when the panorama horizontal option is pressed.
322         * This callback can be null.
323         */
324        public View.OnClickListener panoHorizontalCallback;
325
326        /**
327         * A {@link android.view.View.OnClickListener} that will execute
328         * when the panorama vertical option is pressed.
329         * This callback can be null.
330         */
331        public View.OnClickListener panoVerticalCallback;
332
333        /** Intent UI callbacks */
334
335        /**
336         * A {@link android.view.View.OnClickListener} that will execute
337         * when the cancel option is pressed. This callback can be null.
338         */
339        public View.OnClickListener cancelCallback;
340
341        /**
342         * A {@link android.view.View.OnClickListener} that will execute
343         * when the done option is pressed. This callback can be null.
344         */
345        public View.OnClickListener doneCallback;
346
347        /**
348         * A {@link android.view.View.OnClickListener} that will execute
349         * when the retake option is pressed. This callback can be null.
350         */
351        public View.OnClickListener retakeCallback;
352
353        /**
354         * A {@link android.view.View.OnClickListener} that will execute
355         * when the review option is pressed. This callback can be null.
356         */
357        public View.OnClickListener reviewCallback;
358    }
359
360
361    private final static String TAG = "CameraAppUI";
362
363    private final AppController mController;
364    private final boolean mIsCaptureIntent;
365    private final AnimationManager mAnimationManager;
366
367    // Swipe states:
368    private final static int IDLE = 0;
369    private final static int SWIPE_UP = 1;
370    private final static int SWIPE_DOWN = 2;
371    private final static int SWIPE_LEFT = 3;
372    private final static int SWIPE_RIGHT = 4;
373    private boolean mSwipeEnabled = true;
374
375    // Touch related measures:
376    private final int mSlop;
377    private final static int SWIPE_TIME_OUT_MS = 500;
378
379    private final static int SHIMMY_DELAY_MS = 1000;
380
381    // Mode cover states:
382    private final static int COVER_HIDDEN = 0;
383    private final static int COVER_SHOWN = 1;
384    private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
385    private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3;
386
387    // App level views:
388    private final FrameLayout mCameraRootView;
389    private final ModeTransitionView mModeTransitionView;
390    private final MainActivityLayout mAppRootView;
391    private final ModeListView mModeListView;
392    private final FilmstripLayout mFilmstripLayout;
393    private TextureView mTextureView;
394    private FrameLayout mModuleUI;
395    private BottomBar mBottomBar;
396    private IndicatorOverlay mIndicatorOverlay;
397    private boolean mShouldShowShimmy = false;
398    private IndicatorIconController mIndicatorIconController;
399
400    private TextureViewHelper mTextureViewHelper;
401    private final GestureDetector mGestureDetector;
402    private DisplayManager.DisplayListener mDisplayListener;
403    private int mLastRotation;
404    private int mSwipeState = IDLE;
405    private PreviewOverlay mPreviewOverlay;
406    private CaptureAnimationOverlay mCaptureOverlay;
407    private PreviewStatusListener mPreviewStatusListener;
408    private int mModeCoverState = COVER_HIDDEN;
409    private final FilmstripBottomControls mFilmstripBottomControls;
410    private final FilmstripContentPanel mFilmstripPanel;
411    private Runnable mHideCoverRunnable;
412    private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
413            = new View.OnLayoutChangeListener() {
414        @Override
415        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
416                int oldTop, int oldRight, int oldBottom) {
417            if (mPreviewStatusListener != null) {
418                mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
419                        oldTop, oldRight, oldBottom);
420            }
421        }
422    };
423
424    private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
425
426    public long getCoverHiddenTime() {
427        return mCoverHiddenTime;
428    }
429
430    public void updatePreviewAspectRatio(float aspectRatio) {
431        mTextureViewHelper.updateAspectRatio(aspectRatio);
432    }
433
434    /**
435     * This is to support modules that calculate their own transform matrix because
436     * they need to use a transform matrix to rotate the preview.
437     *
438     * @param matrix transform matrix to be set on the TextureView
439     */
440    public void updatePreviewTransform(Matrix matrix) {
441        mTextureViewHelper.updateTransform(matrix);
442    }
443
444    public interface AnimationFinishedListener {
445        public void onAnimationFinished(boolean success);
446    }
447
448    private class MyTouchListener implements View.OnTouchListener {
449        private boolean mScaleStarted = false;
450        @Override
451        public boolean onTouch(View v, MotionEvent event) {
452            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
453                mScaleStarted = false;
454            } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
455                mScaleStarted = true;
456            }
457            return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
458        }
459    }
460
461    /**
462     * This gesture listener finds out the direction of the scroll gestures and
463     * sends them to CameraAppUI to do further handling.
464     */
465    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
466        private MotionEvent mDown;
467
468        @Override
469        public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
470            if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
471                    || mSwipeState != IDLE
472                    || mIsCaptureIntent
473                    || !mSwipeEnabled) {
474                return false;
475            }
476
477            int deltaX = (int) (ev.getX() - mDown.getX());
478            int deltaY = (int) (ev.getY() - mDown.getY());
479            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
480                if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
481                    // Calculate the direction of the swipe.
482                    if (deltaX >= Math.abs(deltaY)) {
483                        // Swipe right.
484                        setSwipeState(SWIPE_RIGHT);
485                    } else if (deltaX <= -Math.abs(deltaY)) {
486                        // Swipe left.
487                        setSwipeState(SWIPE_LEFT);
488                    } else if (deltaY >= Math.abs(deltaX)) {
489                        // Swipe down.
490                        setSwipeState(SWIPE_DOWN);
491                    } else if (deltaY <= -Math.abs(deltaX)) {
492                        // Swipe up.
493                        setSwipeState(SWIPE_UP);
494                    }
495                }
496            }
497            return true;
498        }
499
500        private void setSwipeState(int swipeState) {
501            mSwipeState = swipeState;
502            // Notify new swipe detected.
503            onSwipeDetected(swipeState);
504        }
505
506        @Override
507        public boolean onDown(MotionEvent ev) {
508            mDown = MotionEvent.obtain(ev);
509            mSwipeState = IDLE;
510            return false;
511        }
512    }
513
514    public CameraAppUI(AppController controller, MainActivityLayout appRootView,
515            boolean isCaptureIntent) {
516        mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
517        mController = controller;
518        mIsCaptureIntent = isCaptureIntent;
519
520        mAppRootView = appRootView;
521        mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
522        mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
523        mModeTransitionView = (ModeTransitionView)
524                mAppRootView.findViewById(R.id.mode_transition_view);
525        mFilmstripBottomControls = new FilmstripBottomControls(
526                (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_controls));
527        mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
528        mGestureDetector = new GestureDetector(controller.getAndroidContext(),
529                new MyGestureListener());
530        mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
531        if (mModeListView != null) {
532            mModeListView.setModeSwitchListener(this);
533            mModeListView.setModeListOpenListener(this);
534        } else {
535            Log.e(TAG, "Cannot find mode list in the view hierarchy");
536        }
537        mAnimationManager = new AnimationManager();
538        initDisplayListener();
539    }
540
541    /**
542     * Enable or disable swipe gestures. We want to disable them e.g. while we
543     * record a video.
544     */
545    public void setSwipeEnabled(boolean enabled) {
546        mAppRootView.setSwipeEnabled(enabled);
547        mSwipeEnabled = enabled;
548    }
549
550    /**
551     * Initializes the display listener to listen to display changes such as
552     * 180-degree rotation change, which will not have an onConfigurationChanged
553     * callback.
554     */
555    private void initDisplayListener() {
556        if (ApiHelper.HAS_DISPLAY_LISTENER) {
557            mLastRotation = CameraUtil.getDisplayRotation(
558                    (Activity) mController.getAndroidContext());
559
560            mDisplayListener = new DisplayManager.DisplayListener() {
561                @Override
562                public void onDisplayAdded(int arg0) {
563                    // Do nothing.
564                }
565
566                @Override
567                public void onDisplayChanged(int displayId) {
568                    int rotation = CameraUtil.getDisplayRotation(
569                            (Activity) mController.getAndroidContext());
570                    if ((rotation - mLastRotation + 360) % 360 == 180
571                            && mPreviewStatusListener != null) {
572                        mPreviewStatusListener.onPreviewFlipped();
573                    }
574                    mLastRotation = rotation;
575                }
576
577                @Override
578                public void onDisplayRemoved(int arg0) {
579                    // Do nothing.
580                }
581            };
582
583            ((DisplayManager) mController.getAndroidContext()
584                    .getSystemService(Context.DISPLAY_SERVICE))
585                    .registerDisplayListener(mDisplayListener, null);
586        }
587    }
588
589    /**
590     * Redirects touch events to appropriate recipient views based on swipe direction.
591     * More specifically, swipe up and swipe down will be handled by the view that handles
592     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
593     * to mode list in order to bring up mode list.
594     */
595    private void onSwipeDetected(int swipeState) {
596        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
597            // Quick switch between modes.
598            int currentModuleIndex = mController.getCurrentModuleIndex();
599            final int moduleToTransitionTo =
600                    mController.getQuickSwitchToModuleId(currentModuleIndex);
601            if (currentModuleIndex != moduleToTransitionTo) {
602                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
603
604                int shadeColorId = CameraUtil.getCameraThemeColorId(moduleToTransitionTo,
605                        mController.getAndroidContext());
606                int iconRes = CameraUtil.getCameraModeIconResId(moduleToTransitionTo,
607                        mController.getAndroidContext());
608
609                AnimationFinishedListener listener = new AnimationFinishedListener() {
610                    @Override
611                    public void onAnimationFinished(boolean success) {
612                        if (success) {
613                            mHideCoverRunnable = new Runnable() {
614                                @Override
615                                public void run() {
616                                    mModeTransitionView.startPeepHoleAnimation();
617                                }
618                            };
619                            mModeCoverState = COVER_SHOWN;
620                            // Go to new module when the previous operation is successful.
621                            mController.onModeSelected(moduleToTransitionTo);
622                        }
623                    }
624                };
625                if (mSwipeState == SWIPE_UP) {
626                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
627                } else {
628                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
629                }
630            }
631        } else if (swipeState == SWIPE_LEFT) {
632            // Pass the touch sequence to filmstrip layout.
633            UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.FILMSTRIP,
634                eventprotos.CameraEvent.InteractionCause.SWIPE_LEFT);
635            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
636        } else if (swipeState == SWIPE_RIGHT) {
637            // Pass the touch to mode switcher
638            mAppRootView.redirectTouchEventsTo(mModeListView);
639        }
640    }
641
642    /**
643     * Gets called when activity resumes in preview.
644     */
645    public void resume() {
646        if (mTextureView == null || mTextureView.getSurfaceTexture() != null) {
647            if (!mIsCaptureIntent) {
648                showShimmyDelayed();
649            }
650        } else {
651            // Show mode theme cover until preview is ready
652            showModeCoverUntilPreviewReady();
653        }
654        // Hide action bar first since we are in full screen mode first, and
655        // switch the system UI to lights-out mode.
656        mFilmstripPanel.hide();
657    }
658
659    /**
660     * A cover view showing the mode theme color and mode icon will be visible on
661     * top of preview until preview is ready (i.e. camera preview is started and
662     * the first frame has been received).
663     */
664    private void showModeCoverUntilPreviewReady() {
665        int modeId = mController.getCurrentModuleIndex();
666        int colorId = CameraUtil.getCameraThemeColorId(modeId, mController.getAndroidContext());
667        int iconId = CameraUtil.getCameraModeIconResId(modeId, mController.getAndroidContext());
668        mModeTransitionView.setupModeCover(colorId, iconId);
669        mHideCoverRunnable = new Runnable() {
670            @Override
671            public void run() {
672                mModeTransitionView.hideModeCover(new AnimationFinishedListener() {
673                    @Override
674                    public void onAnimationFinished(boolean success) {
675                        if (success) {
676                            showShimmyDelayed();
677                        }
678                    }
679                });
680            }
681        };
682        mModeCoverState = COVER_SHOWN;
683    }
684
685    private void showShimmyDelayed() {
686        if (!mIsCaptureIntent) {
687            // Show shimmy in SHIMMY_DELAY_MS
688            mShouldShowShimmy = mController.shouldShowShimmy();
689            if (mShouldShowShimmy) {
690                mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
691            }
692        }
693    }
694
695    private void hideModeCover() {
696        if (mHideCoverRunnable != null) {
697            mAppRootView.post(mHideCoverRunnable);
698            mHideCoverRunnable = null;
699        }
700        mModeCoverState = COVER_HIDDEN;
701        if (mCoverHiddenTime < 0) {
702            mCoverHiddenTime = System.currentTimeMillis();
703        }
704    }
705
706    @Override
707    public void onOpenFullScreen() {
708        if (mShouldShowShimmy) {
709            mController.decrementShimmyPlayTimes();
710            // Sets should show shimmy flag to false for this session (i.e. until
711            // next onResume)
712            mShouldShowShimmy = false;
713        }
714    }
715
716    /**
717     * Called when the back key is pressed.
718     *
719     * @return Whether the UI responded to the key event.
720     */
721    public boolean onBackPressed() {
722        if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
723            return mFilmstripLayout.onBackPressed();
724        } else {
725            return mModeListView.onBackPressed();
726        }
727    }
728
729    /**
730     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
731     * listens to SurfaceTexture changes. In addition, listeners are set on
732     * dependent app ui elements.
733     *
734     * @param previewStatusListener the listener that gets notified when SurfaceTexture
735     *                              changes
736     */
737    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
738        mPreviewStatusListener = previewStatusListener;
739        if (mPreviewStatusListener != null) {
740            onPreviewListenerChanged();
741        }
742    }
743
744    /**
745     * When the PreviewStatusListener changes, listeners need to be
746     * set on the following app ui elements:
747     * {@link com.android.camera.ui.PreviewOverlay},
748     * {@link com.android.camera.ui.BottomBar},
749     * {@link com.android.camera.ui.IndicatorOverlay},
750     * {@link com.android.camera.ui.IndicatorIconController}.
751     */
752    private void onPreviewListenerChanged() {
753        // Set a listener for recognizing preview gestures.
754        GestureDetector.OnGestureListener gestureListener
755            = mPreviewStatusListener.getGestureListener();
756        if (gestureListener != null) {
757            mPreviewOverlay.setGestureListener(gestureListener);
758        }
759
760        // Set a listener for resizing the bottom bar on
761        // preview size changes.
762        mTextureViewHelper.setAutoAdjustTransform(
763            mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
764        if (mPreviewStatusListener.shouldAutoAdjustBottomBar()) {
765            mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
766            mTextureViewHelper.addPreviewAreaSizeChangedListener(mBottomBar);
767        }
768
769        // Set a listener for resizing the indicator overlay on
770        // preview size changes.
771        mIndicatorOverlay = (IndicatorOverlay) mAppRootView.findViewById(
772            R.id.indicator_overlay);
773        mTextureViewHelper.addPreviewAreaSizeChangedListener(mIndicatorOverlay);
774
775        if (mIndicatorIconController == null) {
776            mIndicatorIconController =
777                new IndicatorIconController(mController, mAppRootView);
778        }
779        mController.getSettingsManager().addListener(mIndicatorIconController);
780    }
781
782    /**
783     * This method should be called in onCameraOpened.  It defines CameraAppUI
784     * specific changes that depend on the camera or camera settings.
785     */
786    public void onChangeCamera() {
787        ModuleController moduleController = mController.getCurrentModuleController();
788        if (moduleController.isUsingBottomBar()) {
789            applyModuleSpecs(moduleController.getHardwareSpec(),
790                moduleController.getBottomBarSpec());
791        }
792
793        if (mIndicatorIconController != null) {
794            // Sync the settings state with the indicator state.
795            mIndicatorIconController.syncIndicators();
796        }
797    }
798
799    /**
800     * Adds a listener to receive callbacks when preview area size changes.
801     */
802    public void addPreviewAreaSizeChangedListener(
803            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
804        mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
805    }
806
807    /**
808     * Removes a listener that receives callbacks when preview area size changes.
809     */
810    public void removePreviewAreaSizeChangedListener(
811            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
812        mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
813    }
814
815    /**
816     * This inflates generic_module layout, which contains all the shared views across
817     * modules. Then each module inflates their own views in the given view group. For
818     * now, this is called every time switching from a not-yet-refactored module to a
819     * refactored module. In the future, this should only need to be done once per app
820     * start.
821     */
822    public void prepareModuleUI() {
823        mCameraRootView.removeAllViews();
824        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
825                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
826        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
827
828        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
829        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
830        mTextureViewHelper = new TextureViewHelper(mTextureView);
831        mTextureViewHelper.setSurfaceTextureListener(this);
832        mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
833
834        mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
835        mBottomBar.setupToggle(mIsCaptureIntent);
836
837        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
838        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
839        mPreviewOverlay.setOnPreviewTouchedListener(mBottomBar);
840
841        mCaptureOverlay = (CaptureAnimationOverlay)
842                mCameraRootView.findViewById(R.id.capture_overlay);
843        mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
844        mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
845
846        if (mIndicatorIconController == null) {
847            mIndicatorIconController =
848                new IndicatorIconController(mController, mAppRootView);
849        }
850
851        mController.getButtonManager().load(mCameraRootView);
852        mController.getButtonManager().setListener(mIndicatorIconController);
853    }
854
855    // TODO: Remove this when refactor is done.
856    // This is here to ensure refactored modules can work with not-yet-refactored ones.
857    public void clearCameraUI() {
858        mCameraRootView.removeAllViews();
859        mModuleUI = null;
860        mTextureView = null;
861        mPreviewOverlay = null;
862        mBottomBar = null;
863        mIndicatorOverlay = null;
864        mIndicatorIconController = null;
865        setBottomBarShutterListener(null);
866    }
867
868    /**
869     * Called indirectly from each module in their initialization to get a view group
870     * to inflate the module specific views in.
871     *
872     * @return a view group for modules to attach views to
873     */
874    public FrameLayout getModuleRootView() {
875        // TODO: Change it to mModuleUI when refactor is done
876        return mCameraRootView;
877    }
878
879    /**
880     * Remove all the module specific views.
881     */
882    public void clearModuleUI() {
883        if (mModuleUI != null) {
884            mModuleUI.removeAllViews();
885        }
886        mTextureViewHelper.addPreviewAreaSizeChangedListener(null);
887
888        mPreviewStatusListener = null;
889        mPreviewOverlay.reset();
890    }
891
892    /**
893     * Gets called when preview is ready to start. It sets up one shot preview callback
894     * in order to receive a callback when the preview frame is available, so that
895     * the preview cover can be hidden to reveal preview.
896     *
897     * An alternative for getting the timing to hide preview cover is through
898     * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
899     * which is less accurate but therefore is the fallback for modules that manage
900     * their own preview callbacks (as setting one preview callback will override
901     * any other installed preview callbacks), or use camera2 API.
902     */
903    public void onPreviewReadyToStart() {
904        if (mModeCoverState == COVER_SHOWN) {
905            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
906            mController.setupOneShotPreviewListener();
907        }
908    }
909
910    /**
911     * Gets called when preview is started.
912     */
913    public void onPreviewStarted() {
914        if (mModeCoverState == COVER_SHOWN) {
915            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
916        }
917    }
918
919    /**
920     * Gets notified when next preview frame comes in.
921     */
922    public void onNewPreviewFrame() {
923        hideModeCover();
924        mModeCoverState = COVER_HIDDEN;
925    }
926
927    /**
928     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
929     *
930     * @param modeIndex mode index of the selected mode
931     */
932    @Override
933    public void onModeSelected(int modeIndex) {
934        mHideCoverRunnable = new Runnable() {
935            @Override
936            public void run() {
937                mModeListView.startModeSelectionAnimation();
938            }
939        };
940        mModeCoverState = COVER_SHOWN;
941
942        int lastIndex = mController.getCurrentModuleIndex();
943        mController.onModeSelected(modeIndex);
944        int currentIndex = mController.getCurrentModuleIndex();
945
946        if (mTextureView == null) {
947            // TODO: Remove this when all the modules use TextureView
948            int temporaryDelay = 600; // ms
949            mModeListView.postDelayed(new Runnable() {
950                @Override
951                public void run() {
952                    hideModeCover();
953                }
954            }, temporaryDelay);
955        } else if (lastIndex == currentIndex) {
956            hideModeCover();
957        }
958    }
959
960    /********************** Capture animation **********************/
961    /* TODO: This session is subject to UX changes. In addition to the generic
962       flash animation and post capture animation, consider designating a parameter
963       for specifying the type of animation, as well as an animation finished listener
964       so that modules can have more knowledge of the status of the animation. */
965
966    /**
967     * Starts the pre-capture animation.
968     */
969    public void startPreCaptureAnimation() {
970        mCaptureOverlay.startFlashAnimation();
971    }
972
973    /**
974     * Cancels the pre-capture animation.
975     */
976    public void cancelPreCaptureAnimation() {
977        mAnimationManager.cancelAnimations();
978    }
979
980    /**
981     * Cancels the post-capture animation.
982     */
983    public void cancelPostCaptureAnimation() {
984        mAnimationManager.cancelAnimations();
985    }
986
987    public FilmstripContentPanel getFilmstripContentPanel() {
988        return mFilmstripPanel;
989    }
990
991    /**
992     * @return The {@link com.android.camera.app.CameraAppUI.BottomControls} on the
993     * bottom of the filmstrip.
994     */
995    public BottomControls getFilmstripBottomControls() {
996        return mFilmstripBottomControls;
997    }
998
999    /**
1000     * @param listener The listener for bottom controls.
1001     */
1002    public void setFilmstripBottomControlsListener(BottomControls.Listener listener) {
1003        mFilmstripBottomControls.setListener(listener);
1004    }
1005
1006    /***************************SurfaceTexture Listener*********************************/
1007
1008    @Override
1009    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1010        Log.v(TAG, "SurfaceTexture is available");
1011        if (mPreviewStatusListener != null) {
1012            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1013        }
1014    }
1015
1016    @Override
1017    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1018        if (mPreviewStatusListener != null) {
1019            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1020        }
1021    }
1022
1023    @Override
1024    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1025        Log.v(TAG, "SurfaceTexture is destroyed");
1026        if (mPreviewStatusListener != null) {
1027            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1028        }
1029        return false;
1030    }
1031
1032    @Override
1033    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1034        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1035            hideModeCover();
1036            mModeCoverState = COVER_HIDDEN;
1037        }
1038        if (mPreviewStatusListener != null) {
1039            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1040        }
1041    }
1042
1043    /**
1044     * Sets the color of the bottom bar.
1045     */
1046    public void setBottomBarColor(int colorId) {
1047        mBottomBar.setBackgroundColor(colorId);
1048    }
1049
1050    /**
1051     * Sets the pressed color of the bottom bar.
1052     */
1053    public void setBottomBarPressedColor(int colorId) {
1054        mBottomBar.setBackgroundPressedColor(colorId);
1055    }
1056
1057    // TODO: refactor this out so it can controlled by the app.
1058    /**
1059     * Sets the shutter button icon on the bottom bar
1060     */
1061    public void setBottomBarShutterIcon(int shutterIconId) {
1062        mBottomBar.setShutterButtonIcon(shutterIconId);
1063    }
1064
1065    public void animateBottomBarToCircle(int shutterIconId) {
1066        mBottomBar.animateToCircle(shutterIconId);
1067    }
1068
1069    public void animateBottomBarToFullSize(int shutterIconId) {
1070        mBottomBar.animateToFullSize(shutterIconId);
1071    }
1072
1073    /**
1074     * Set the visibility of the bottom bar.
1075     */
1076    // TODO: needed for when panorama is managed by the generic module ui.
1077    public void setBottomBarVisible(boolean visible) {
1078        mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1079    }
1080
1081    /**
1082     * If the bottom bar is visible (hence has been drawn),
1083     * this sets a {@link #ShutterButton.OnShutterButtonListener}
1084     * on the global shutter button,
1085     */
1086    public void setBottomBarShutterListener(
1087            ShutterButton.OnShutterButtonListener listener) {
1088        ShutterButton shutterButton
1089            = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1090        if (shutterButton != null) {
1091            shutterButton.setOnShutterButtonListener(listener);
1092        }
1093    }
1094
1095    /**
1096     * Performs a transition to the global intent layout.
1097     */
1098    public void transitionToIntentLayout() {
1099        ModuleController moduleController = mController.getCurrentModuleController();
1100        if (moduleController.isUsingBottomBar()) {
1101            applyModuleSpecs(moduleController.getHardwareSpec(),
1102                moduleController.getBottomBarSpec());
1103            mBottomBar.transitionToIntentLayout();
1104        }
1105    }
1106
1107    /**
1108     * Performs a transition to the global intent review layout.
1109     */
1110    public void transitionToIntentReviewLayout() {
1111        ModuleController moduleController = mController.getCurrentModuleController();
1112        if (moduleController.isUsingBottomBar()) {
1113            applyModuleSpecs(moduleController.getHardwareSpec(),
1114                moduleController.getBottomBarSpec());
1115            mBottomBar.transitionToIntentReviewLayout();
1116        }
1117    }
1118
1119    /**
1120     * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1121     * to the bottom bar mode options based on limitations from a
1122     * {@link com.android.camera.hardware.HardwareSpec}.
1123     *
1124     * Options not supported by the hardware are either hidden
1125     * or disabled, depending on the option.
1126     *
1127     * Otherwise, the option is fully enabled and clickable.
1128     */
1129    public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1130           final BottomBarUISpec bottomBarSpec) {
1131        if (hardwareSpec == null || bottomBarSpec == null) {
1132            return;
1133        }
1134
1135        ButtonManager buttonManager = mController.getButtonManager();
1136        SettingsManager settingsManager = mController.getSettingsManager();
1137
1138        /** Standard mode options */
1139        if (hardwareSpec.isFrontCameraSupported()) {
1140            if (bottomBarSpec.enableCamera) {
1141                buttonManager.enableButton(ButtonManager.BUTTON_CAMERA,
1142                    bottomBarSpec.cameraCallback);
1143            } else {
1144                buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1145            }
1146        } else {
1147            // Hide camera icon if front camera not available.
1148            buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1149        }
1150
1151        if (hardwareSpec.isFlashSupported()) {
1152            if (bottomBarSpec.enableFlash && settingsManager.isCameraBackFacing()) {
1153                buttonManager.enableButton(ButtonManager.BUTTON_FLASH, bottomBarSpec.flashCallback);
1154            } else if (bottomBarSpec.enableTorchFlash && settingsManager.isCameraBackFacing()) {
1155                buttonManager.enableButton(ButtonManager.BUTTON_TORCH, bottomBarSpec.flashCallback);
1156            } else {
1157                buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1158            }
1159        } else {
1160            // Disable flash icon if not supported by the hardware.
1161            buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1162        }
1163
1164        if (bottomBarSpec.hideHdr) {
1165            // Force hide hdr or hdr plus icon.
1166            buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1167        } else {
1168            if (hardwareSpec.isHdrPlusSupported()) {
1169                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1170                    buttonManager.enableButton(ButtonManager.BUTTON_HDRPLUS,
1171                        bottomBarSpec.hdrCallback);
1172                } else {
1173                    buttonManager.disableButton(ButtonManager.BUTTON_HDRPLUS);
1174                }
1175            } else if (hardwareSpec.isHdrSupported()) {
1176                if (bottomBarSpec.enableHdr && settingsManager.isCameraBackFacing()) {
1177                    buttonManager.enableButton(ButtonManager.BUTTON_HDR,
1178                        bottomBarSpec.hdrCallback);
1179                } else {
1180                    buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1181                }
1182            } else {
1183                // Hide hdr plus or hdr icon if neither are supported.
1184                buttonManager.hideButton(ButtonManager.BUTTON_HDRPLUS);
1185            }
1186        }
1187
1188        if (bottomBarSpec.hideRefocus) {
1189            buttonManager.hideButton(ButtonManager.BUTTON_REFOCUS);
1190        } else {
1191            if (bottomBarSpec.enableRefocus) {
1192                buttonManager.enableButton(ButtonManager.BUTTON_REFOCUS,
1193                    bottomBarSpec.refocusCallback);
1194            } else {
1195                // Disable refocus icon when not enabled, not dependent
1196                // on hardware spec.
1197                buttonManager.disableButton(ButtonManager.BUTTON_REFOCUS);
1198            }
1199        }
1200
1201        if (bottomBarSpec.enablePanoHorizontal
1202                && PhotoSphereHelper.getPanoramaHorizontalDrawableId() > 0) {
1203            buttonManager.enablePushButton(ButtonManager.BUTTON_PANO_HORIZONTAL,
1204                bottomBarSpec.panoHorizontalCallback,
1205                PhotoSphereHelper.getPanoramaHorizontalDrawableId());
1206        }
1207
1208        if (bottomBarSpec.enablePanoVertical
1209                && PhotoSphereHelper.getPanoramaVerticalDrawableId() > 0) {
1210            buttonManager.enablePushButton(ButtonManager.BUTTON_PANO_VERTICAL,
1211                bottomBarSpec.panoVerticalCallback,
1212                PhotoSphereHelper.getPanoramaVerticalDrawableId());
1213        }
1214
1215        /** Intent UI */
1216        if (bottomBarSpec.showCancel) {
1217            buttonManager.enablePushButton(ButtonManager.BUTTON_CANCEL,
1218                bottomBarSpec.cancelCallback);
1219        }
1220        if (bottomBarSpec.showDone) {
1221            buttonManager.enablePushButton(ButtonManager.BUTTON_DONE,
1222                bottomBarSpec.doneCallback);
1223        }
1224        if (bottomBarSpec.showRetake) {
1225            buttonManager.enablePushButton(ButtonManager.BUTTON_RETAKE,
1226                bottomBarSpec.retakeCallback);
1227        }
1228    }
1229}
1230