CameraAppUI.java revision 4a010db8a60008b2bf67b93b64f77f63affc29f3
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.RectF;
22import android.graphics.SurfaceTexture;
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;
32import android.widget.FrameLayout.LayoutParams;
33
34import com.android.camera.AnimationManager;
35import com.android.camera.filmstrip.FilmstripContentPanel;
36import com.android.camera.ui.BottomBar;
37import com.android.camera.ui.CaptureAnimationOverlay;
38import com.android.camera.ui.MainActivityLayout;
39import com.android.camera.ui.ModeListView;
40import com.android.camera.ui.ModeTransitionView;
41import com.android.camera.ui.PreviewOverlay;
42import com.android.camera.ui.PreviewStatusListener;
43import com.android.camera.widget.FilmstripLayout;
44import com.android.camera2.R;
45
46/**
47 * CameraAppUI centralizes control of views shared across modules. Whereas module
48 * specific views will be handled in each Module UI. For example, we can now
49 * bring the flash animation and capture animation up from each module to app
50 * level, as these animations are largely the same for all modules.
51 *
52 * This class also serves to disambiguate touch events. It recognizes all the
53 * swipe gestures that happen on the preview by attaching a touch listener to
54 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
55 * of how swipe from each direction should be handled, it can then redirect these
56 * events to appropriate recipient views.
57 */
58public class CameraAppUI implements ModeListView.ModeSwitchListener,
59        TextureView.SurfaceTextureListener {
60
61    /**
62     * The bottom controls on the filmstrip.
63     */
64    public static interface BottomControls {
65        /** Values for the view state of the button. */
66        public final int VIEW_NONE = 0;
67        public final int VIEW_PHOTO_SPHERE = 1;
68        public final int VIEW_RGBZ = 2;
69
70        /**
71         * Sets a new or replaces an existing listener for bottom control events.
72         */
73        void setListener(Listener listener);
74
75        /**
76         * Set if the bottom controls are visible.
77         * @param visible {@code true} if visible.
78         */
79        void setVisible(boolean visible);
80
81        /**
82         * @param visible Whether the button is visible.
83         */
84        void setEditButtonVisibility(boolean visible);
85
86        /**
87         * @param enabled Whether the button is enabled.
88         */
89        void setEditEnabled(boolean enabled);
90
91        /**
92         * Sets the visibility of the view-photosphere button.
93         *
94         * @param state one of {@link #VIEW_NONE}, {@link #VIEW_PHOTO_SPHERE},
95         *            {@link #VIEW_RGBZ}.
96         */
97        void setViewButtonVisibility(int state);
98
99        /**
100         * @param enabled Whether the button is enabled.
101         */
102        void setViewEnabled(boolean enabled);
103
104        /**
105         * @param visible Whether the button is visible.
106         */
107        void setTinyPlanetButtonVisibility(boolean visible);
108
109        /**
110         * @param enabled Whether the button is enabled.
111         */
112        void setTinyPlanetEnabled(boolean enabled);
113
114        /**
115         * @param visible Whether the button is visible.
116         */
117        void setDeleteButtonVisibility(boolean visible);
118
119        /**
120         * @param enabled Whether the button is enabled.
121         */
122        void setDeleteEnabled(boolean enabled);
123
124        /**
125         * @param visible Whether the button is visible.
126         */
127        void setShareButtonVisibility(boolean visible);
128
129        /**
130         * @param enabled Whether the button is enabled.
131         */
132        void setShareEnabled(boolean enabled);
133
134        /**
135         * @param visible Whether the button is visible.
136         */
137        void setGalleryButtonVisibility(boolean visible);
138
139        /**
140         * Classes implementing this interface can listen for events on the bottom
141         * controls.
142         */
143        public static interface Listener {
144            /**
145             * Called when the user pressed the "view" button to e.g. view a photo
146             * sphere or RGBZ image.
147             */
148            public void onView();
149
150            /**
151             * Called when the "edit" button is pressed.
152             */
153            public void onEdit();
154
155            /**
156             * Called when the "tiny planet" button is pressed.
157             */
158            public void onTinyPlanet();
159
160            /**
161             * Called when the "delete" button is pressed.
162             */
163            public void onDelete();
164
165            /**
166             * Called when the "share" button is pressed.
167             */
168            public void onShare();
169
170            /**
171             * Called when the "gallery" button is pressed.
172             */
173            public void onGallery();
174        }
175    }
176
177    private final static String TAG = "CameraAppUI";
178
179    private final AppController mController;
180    private final boolean mIsCaptureIntent;
181    private final boolean mIsSecureCamera;
182    private final AnimationManager mAnimationManager;
183
184    // Swipe states:
185    private final static int IDLE = 0;
186    private final static int SWIPE_UP = 1;
187    private final static int SWIPE_DOWN = 2;
188    private final static int SWIPE_LEFT = 3;
189    private final static int SWIPE_RIGHT = 4;
190
191    // Touch related measures:
192    private final int mSlop;
193    private final static int SWIPE_TIME_OUT_MS = 500;
194
195    private final static int SHIMMY_DELAY_MS = 1000;
196
197    // Mode cover states:
198    private final static int COVER_HIDDEN = 0;
199    private final static int COVER_SHOWN = 1;
200    private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
201
202    // App level views:
203    private final FrameLayout mCameraRootView;
204    private final ModeTransitionView mModeTransitionView;
205    private final MainActivityLayout mAppRootView;
206    private final ModeListView mModeListView;
207    private final FilmstripLayout mFilmstripLayout;
208    private TextureView mTextureView;
209    private View mFlashOverlay;
210    private FrameLayout mModuleUI;
211
212    private final GestureDetector mGestureDetector;
213    private int mSwipeState = IDLE;
214    private PreviewOverlay mPreviewOverlay;
215    private CaptureAnimationOverlay mCaptureOverlay;
216    private PreviewStatusListener mPreviewStatusListener;
217    private int mModeCoverState = COVER_HIDDEN;
218    private final FilmstripBottomControls mFilmstripBottomControls;
219    private final FilmstripContentPanel mFilmstripPanel;
220    private Runnable mHideCoverRunnable;
221    private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
222            = new View.OnLayoutChangeListener() {
223        @Override
224        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
225                int oldTop, int oldRight, int oldBottom) {
226            if (mPreviewStatusListener != null) {
227                mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
228                        oldTop, oldRight, oldBottom);
229            }
230        }
231    };
232
233    // TODO this isn't used by all modules universally, should be part of a util class or something
234    /**
235     * Resizes the preview texture and given bottom bar for 100% preview size
236     */
237    public void adjustPreviewAndBottomBarSize(int width, int height,
238            BottomBar bottomBar, float aspectRatio,
239            int bottomBarMinHeight, int bottomBarOptimalHeight) {
240        Matrix matrix = mTextureView.getTransform(null);
241
242        float scaleX = 1f, scaleY = 1f;
243        float scaledTextureWidth, scaledTextureHeight;
244        if (width > height) {
245            scaledTextureWidth = Math.min(width,
246                                          (int) (height * aspectRatio));
247            scaledTextureHeight = Math.min(height,
248                                           (int) (width / aspectRatio));
249        } else {
250            scaledTextureWidth = Math.min(width,
251                                          (int) (height / aspectRatio));
252            scaledTextureHeight = Math.min(height,
253                                           (int) (width * aspectRatio));
254        }
255
256        scaleX = scaledTextureWidth / width;
257        scaleY = scaledTextureHeight / height;
258
259        // TODO: Need a better way to find out whether currently in landscape
260        boolean landscape = width > height;
261        if (landscape) {
262            matrix.setScale(scaleX, scaleY, 0f, (float) height / 2);
263        } else {
264            matrix.setScale(scaleX, scaleY, (float) width / 2, 0.0f);
265        }
266        setPreviewTransformMatrix(matrix);
267        adjustBottomBar(width, height, bottomBar, bottomBarOptimalHeight, scaledTextureWidth,
268                scaledTextureHeight, landscape);
269    }
270
271    private void adjustBottomBar(int width, int height, BottomBar bottomBar,
272                                 int bottomBarOptimalHeight, float scaledTextureWidth,
273                                 float scaledTextureHeight, boolean landscape) {
274        float previewAspectRatio =
275                scaledTextureWidth / scaledTextureHeight;
276        if (previewAspectRatio < 1.0) {
277            previewAspectRatio = 1.0f/previewAspectRatio;
278        }
279        float screenAspectRatio = (float)width / (float)height;
280        if (screenAspectRatio < 1.0) {
281            screenAspectRatio = 1.0f/screenAspectRatio;
282        }
283
284        if(bottomBar != null) {
285            LayoutParams lp = (LayoutParams) bottomBar.getLayoutParams();
286            // TODO accoount for cases where resizes bar height would be < bottomBarMinHeight
287            if (previewAspectRatio >= screenAspectRatio) {
288                bottomBar.setAlpha(0.5f);
289                if (landscape) {
290                    lp.width = bottomBarOptimalHeight;
291                    lp.height = LayoutParams.MATCH_PARENT;
292                } else {
293                    lp.height = bottomBarOptimalHeight;
294                    lp.width = LayoutParams.MATCH_PARENT;
295                }
296            } else {
297                bottomBar.setAlpha(1.0f);
298                if (landscape) {
299                    lp.width = (int)(width - scaledTextureWidth);
300                    lp.height = LayoutParams.MATCH_PARENT;
301                } else {
302                    lp.height = (int)(height - scaledTextureHeight);
303                    lp.width = LayoutParams.MATCH_PARENT;
304                }
305            }
306            bottomBar.setLayoutParams(lp);
307        }
308    }
309
310    /**
311     * This is to support modules that calculate their own transform matrix because
312     * they need to use a transform matrix to rotate the preview.
313     *
314     * @param width width of the TextureView where preview is hosted
315     * @param height height of the TextureView where preview is hosted
316     * @param matrix transform matrix to be set on the TextureView
317     */
318    public void updatePreviewTransform(int width, int height, Matrix matrix) {
319        if (width == 0 || height == 0) {
320            Log.e(TAG, "Invalid screen size: " + width + " x " + height);
321            return;
322        }
323        int bottomBarMinHeight = mCameraRootView.getResources()
324                .getDimensionPixelSize(R.dimen.bottom_bar_height_min);
325        int bottomBarOptimalHeight = mCameraRootView.getResources()
326                .getDimensionPixelSize(R.dimen.bottom_bar_height_optimal);
327        RectF previewRect = new RectF(0, 0, width, height);
328        matrix.mapRect(previewRect);
329
330        float previewWidth = previewRect.width();
331        float previewHeight = previewRect.height();
332        if (previewHeight == 0 || previewWidth == 0) {
333            Log.e(TAG, "Invalid preview size: " + previewWidth + " x " + previewHeight);
334            return;
335        }
336
337        BottomBar bottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
338        setPreviewTransformMatrix(matrix);
339        adjustBottomBar(width, height, bottomBar, bottomBarOptimalHeight, previewWidth,
340                previewHeight, width > height);
341    }
342
343    public interface AnimationFinishedListener {
344        public void onAnimationFinished(boolean success);
345    }
346
347    private class MyTouchListener implements View.OnTouchListener {
348        private boolean mScaleStarted = false;
349        @Override
350        public boolean onTouch(View v, MotionEvent event) {
351            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
352                mScaleStarted = false;
353            } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
354                mScaleStarted = true;
355            }
356            return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
357        }
358    }
359
360    /**
361     * This gesture listener finds out the direction of the scroll gestures and
362     * sends them to CameraAppUI to do further handling.
363     */
364    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
365        private MotionEvent mDown;
366
367        @Override
368        public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
369            if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
370                    || mSwipeState != IDLE) {
371                return false;
372            }
373
374            int deltaX = (int) (ev.getX() - mDown.getX());
375            int deltaY = (int) (ev.getY() - mDown.getY());
376            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
377                if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
378                    // Calculate the direction of the swipe.
379                    if (deltaX >= Math.abs(deltaY)) {
380                        // Swipe right.
381                        setSwipeState(SWIPE_RIGHT);
382                    } else if (deltaX <= -Math.abs(deltaY)) {
383                        // Swipe left.
384                        setSwipeState(SWIPE_LEFT);
385                    } else if (deltaY >= Math.abs(deltaX)) {
386                        // Swipe down.
387                        setSwipeState(SWIPE_DOWN);
388                    } else if (deltaY <= -Math.abs(deltaX)) {
389                        // Swipe up.
390                        setSwipeState(SWIPE_UP);
391                    }
392                }
393            }
394            return true;
395        }
396
397        private void setSwipeState(int swipeState) {
398            mSwipeState = swipeState;
399            // Notify new swipe detected.
400            onSwipeDetected(swipeState);
401        }
402
403        @Override
404        public boolean onDown(MotionEvent ev) {
405            mDown = MotionEvent.obtain(ev);
406            mSwipeState = IDLE;
407            return false;
408        }
409    }
410
411    public CameraAppUI(AppController controller, MainActivityLayout appRootView,
412                       boolean isSecureCamera, boolean isCaptureIntent) {
413        mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
414        mController = controller;
415        mIsSecureCamera = isSecureCamera;
416        mIsCaptureIntent = isCaptureIntent;
417
418        mAppRootView = appRootView;
419        mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
420        mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
421        mModeTransitionView = (ModeTransitionView)
422                mAppRootView.findViewById(R.id.mode_transition_view);
423        mFilmstripBottomControls = new FilmstripBottomControls(
424                (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_controls));
425        mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
426        mGestureDetector = new GestureDetector(controller.getAndroidContext(),
427                new MyGestureListener());
428        mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
429        if (mModeListView != null) {
430            mModeListView.setModeSwitchListener(this);
431        } else {
432            Log.e(TAG, "Cannot find mode list in the view hierarchy");
433        }
434        mAnimationManager = new AnimationManager();
435    }
436
437    /**
438     * Redirects touch events to appropriate recipient views based on swipe direction.
439     * More specifically, swipe up and swipe down will be handled by the view that handles
440     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
441     * to mode list in order to bring up mode list.
442     */
443    private void onSwipeDetected(int swipeState) {
444        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
445            // Quick switch between photo/video.
446            if (mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO ||
447                    mController.getCurrentModuleIndex() == ModeListView.MODE_VIDEO) {
448                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
449
450                final int moduleToTransitionTo =
451                        mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO ?
452                        ModeListView.MODE_VIDEO : ModeListView.MODE_PHOTO;
453                int shadeColorId = ModeListView.getModeThemeColor(moduleToTransitionTo);
454                int iconRes = ModeListView.getModeIconResourceId(moduleToTransitionTo);
455
456                AnimationFinishedListener listener = new AnimationFinishedListener() {
457                    @Override
458                    public void onAnimationFinished(boolean success) {
459                        if (success) {
460                            mHideCoverRunnable = new Runnable() {
461                                @Override
462                                public void run() {
463                                    mModeTransitionView.startPeepHoleAnimation();
464                                }
465                            };
466                            mModeCoverState = COVER_SHOWN;
467                            // Go to new module when the previous operation is successful.
468                            mController.onModeSelected(moduleToTransitionTo);
469                        }
470                    }
471                };
472                if (mSwipeState == SWIPE_UP) {
473                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
474                } else {
475                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
476                }
477            }
478        } else if (swipeState == SWIPE_LEFT) {
479            // Pass the touch sequence to filmstrip layout.
480            mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
481
482        } else if (swipeState == SWIPE_RIGHT) {
483            // Pass the touch to mode switcher
484            mAppRootView.redirectTouchEventsTo(mModeListView);
485        }
486    }
487
488    /**
489     * Gets called when activity resumes in preview.
490     */
491    public void resume() {
492        if (mTextureView == null || mTextureView.getSurfaceTexture() != null) {
493            mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
494        } else {
495            // Show mode theme cover until preview is ready
496            showModeCoverUntilPreviewReady();
497        }
498        // Hide action bar first since we are in full screen mode first, and
499        // switch the system UI to lights-out mode.
500        mFilmstripPanel.hide();
501    }
502
503    /**
504     * A cover view showing the mode theme color and mode icon will be visible on
505     * top of preview until preview is ready (i.e. camera preview is started and
506     * the first frame has been received).
507     */
508    private void showModeCoverUntilPreviewReady() {
509        int modeId = mController.getCurrentModuleIndex();
510        int colorId = ModeListView.getModeThemeColor(modeId);
511        int iconId = ModeListView.getModeIconResourceId(modeId);
512        mModeTransitionView.setupModeCover(colorId, iconId);
513        mHideCoverRunnable = new Runnable() {
514            @Override
515            public void run() {
516                mModeTransitionView.hideModeCover(new AnimationFinishedListener() {
517                    @Override
518                    public void onAnimationFinished(boolean success) {
519                        if (success) {
520                            // Show shimmy in SHIMMY_DELAY_MS
521                            mModeListView.startAccordionAnimationWithDelay(SHIMMY_DELAY_MS);
522                        }
523                    }
524                });
525            }
526        };
527        mModeCoverState = COVER_SHOWN;
528    }
529
530    private void hideModeCover() {
531        if (mHideCoverRunnable != null) {
532            mAppRootView.post(mHideCoverRunnable);
533            mHideCoverRunnable = null;
534        }
535        mModeCoverState = COVER_HIDDEN;
536    }
537
538    /**
539     * Called when the back key is pressed.
540     *
541     * @return Whether the UI responded to the key event.
542     */
543    public boolean onBackPressed() {
544        return mFilmstripLayout.onBackPressed();
545    }
546
547    /**
548     * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
549     * listens to SurfaceTexture changes. In addition, the listener will also provide
550     * a {@link android.view.GestureDetector.OnGestureListener}, which will listen to
551     * gestures that happen on camera preview.
552     *
553     * @param previewStatusListener the listener that gets notified when SurfaceTexture
554     *                              changes
555     */
556    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
557        mPreviewStatusListener = previewStatusListener;
558        if (mPreviewStatusListener != null) {
559            GestureDetector.OnGestureListener gestureListener
560                    = mPreviewStatusListener.getGestureListener();
561            if (gestureListener != null) {
562                mPreviewOverlay.setGestureListener(gestureListener);
563            }
564        }
565    }
566
567    /**
568     * This inflates generic_module layout, which contains all the shared views across
569     * modules. Then each module inflates their own views in the given view group. For
570     * now, this is called every time switching from a not-yet-refactored module to a
571     * refactored module. In the future, this should only need to be done once per app
572     * start.
573     */
574    public void prepareModuleUI() {
575        mCameraRootView.removeAllViews();
576        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
577                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
578        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
579
580        mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
581        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
582        mTextureView.addOnLayoutChangeListener(mPreviewLayoutChangeListener);
583        mTextureView.setSurfaceTextureListener(this);
584        mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
585        mPreviewOverlay.setOnTouchListener(new MyTouchListener());
586        mCaptureOverlay = (CaptureAnimationOverlay)
587                mCameraRootView.findViewById(R.id.capture_overlay);
588    }
589
590    // TODO: Remove this when refactor is done.
591    // This is here to ensure refactored modules can work with not-yet-refactored ones.
592    public void clearCameraUI() {
593        mCameraRootView.removeAllViews();
594        mModuleUI = null;
595        mTextureView.removeOnLayoutChangeListener(mPreviewLayoutChangeListener);
596        mTextureView = null;
597        mPreviewOverlay = null;
598        mFlashOverlay = null;
599    }
600
601    /**
602     * Called indirectly from each module in their initialization to get a view group
603     * to inflate the module specific views in.
604     *
605     * @return a view group for modules to attach views to
606     */
607    public FrameLayout getModuleRootView() {
608        // TODO: Change it to mModuleUI when refactor is done
609        return mCameraRootView;
610    }
611
612    /**
613     * Remove all the module specific views.
614     */
615    public void clearModuleUI() {
616        if (mModuleUI != null) {
617            mModuleUI.removeAllViews();
618        }
619
620        mPreviewStatusListener = null;
621        mPreviewOverlay.reset();
622    }
623
624    /**
625     * Gets called when preview is started.
626     */
627    public void onPreviewStarted() {
628        if (mModeCoverState == COVER_SHOWN) {
629            mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
630        }
631    }
632
633    /**
634     * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
635     *
636     * @param modeIndex mode index of the selected mode
637     */
638    @Override
639    public void onModeSelected(int modeIndex) {
640        mHideCoverRunnable = new Runnable() {
641            @Override
642            public void run() {
643                mModeListView.startModeSelectionAnimation();
644            }
645        };
646        mModeCoverState = COVER_SHOWN;
647
648        int lastIndex = mController.getCurrentModuleIndex();
649        mController.onModeSelected(modeIndex);
650        int currentIndex = mController.getCurrentModuleIndex();
651
652        if (mTextureView == null) {
653            // TODO: Remove this when all the modules use TextureView
654            int temporaryDelay = 600; // ms
655            mModeListView.postDelayed(new Runnable() {
656                @Override
657                public void run() {
658                    hideModeCover();
659                }
660            }, temporaryDelay);
661        } else if (lastIndex == currentIndex) {
662            hideModeCover();
663        }
664    }
665
666    /**
667     * Sets the transform matrix on the preview TextureView
668     */
669    public void setPreviewTransformMatrix(Matrix transformMatrix) {
670        if (mTextureView == null) {
671            throw new UnsupportedOperationException("Cannot set transform matrix on a null" +
672                    " TextureView");
673        }
674        mTextureView.setTransform(transformMatrix);
675    }
676
677
678    /********************** Capture animation **********************/
679    /* TODO: This session is subject to UX changes. In addition to the generic
680       flash animation and post capture animation, consider designating a parameter
681       for specifying the type of animation, as well as an animation finished listener
682       so that modules can have more knowledge of the status of the animation. */
683
684    /**
685     * Starts the pre-capture animation.
686     */
687    public void startPreCaptureAnimation() {
688        mCaptureOverlay.startFlashAnimation();
689    }
690
691    /**
692     * Cancels the pre-capture animation.
693     */
694    public void cancelPreCaptureAnimation() {
695        mAnimationManager.cancelAnimations();
696    }
697
698    /**
699     * Cancels the post-capture animation.
700     */
701    public void cancelPostCaptureAnimation() {
702        mAnimationManager.cancelAnimations();
703    }
704
705    public FilmstripContentPanel getFilmstripContentPanel() {
706        return mFilmstripPanel;
707    }
708
709    /**
710     * @return The {@link com.android.camera.app.CameraAppUI.BottomControls} on the
711     * bottom of the filmstrip.
712     */
713    public BottomControls getFilmstripBottomControls() {
714        return mFilmstripBottomControls;
715    }
716
717    /**
718     * @param listener The listener for bottom controls.
719     */
720    public void setFilmstripBottomControlsListener(BottomControls.Listener listener) {
721        mFilmstripBottomControls.setListener(listener);
722    }
723
724    /***************************SurfaceTexture Listener*********************************/
725
726    @Override
727    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
728        Log.v(TAG, "SurfaceTexture is available");
729        if (mPreviewStatusListener != null) {
730            mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
731        }
732    }
733
734    @Override
735    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
736        if (mPreviewStatusListener != null) {
737            mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
738        }
739    }
740
741    @Override
742    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
743        Log.v(TAG, "SurfaceTexture is destroyed");
744        if (mPreviewStatusListener != null) {
745            return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
746        }
747        return false;
748    }
749
750    @Override
751    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
752        if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_FRAME) {
753            hideModeCover();
754            mModeCoverState = COVER_HIDDEN;
755        }
756        if (mPreviewStatusListener != null) {
757            mPreviewStatusListener.onSurfaceTextureUpdated(surface);
758        }
759    }
760}
761