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