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