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