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