CameraAppUI.java revision f55f3c461c5a6ae6b61fa75562ca01683aa93f9a
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.Bitmap;
21import android.graphics.Matrix;
22import android.util.Log;
23import android.view.GestureDetector;
24import android.view.LayoutInflater;
25import android.view.MotionEvent;
26import android.view.TextureView;
27import android.view.View;
28import android.view.ViewConfiguration;
29import android.view.ViewGroup;
30import android.widget.ImageView;
31
32import com.android.camera.AnimationManager;
33import com.android.camera.ui.CameraControls;
34import com.android.camera.ui.MainActivityLayout;
35import com.android.camera.ui.ModeListView;
36import com.android.camera.ui.ModeTransitionView;
37import com.android.camera.ui.RenderOverlay;
38import com.android.camera2.R;
39
40/**
41 * CameraAppUI centralizes control of views shared across modules. Whereas module
42 * specific views will be handled in each Module UI. For example, we can now
43 * bring the flash animation and capture animation up from each module to app
44 * level, as these animations are largely the same for all modules.
45 *
46 * This class also serves to disambiguate touch events. It recognizes all the
47 * swipe gestures that happen on the preview by attaching a touch listener to
48 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
49 * of how swipe from each direction should be handled, it can then redirect these
50 * events to appropriate recipient views.
51 */
52public class CameraAppUI implements ModeListView.ModeSwitchListener {
53    private final static String TAG = "CameraAppUI";
54
55    private final AppController mController;
56    private final boolean mIsCaptureIntent;
57    private final boolean mIsSecureCamera;
58    private final AnimationManager mAnimationManager;
59
60    // Swipe states:
61    private final int IDLE = 0;
62    private final int SWIPE_UP = 1;
63    private final int SWIPE_DOWN = 2;
64    private final int SWIPE_LEFT = 3;
65    private final int SWIPE_RIGHT = 4;
66
67    // Touch related measures:
68    private final int mSlop;
69    private final int SWIPE_TIME_OUT = 500;
70
71    // App level views:
72    private final ViewGroup mCameraRootView;
73    private final ModeTransitionView mModeTransitionView;
74    private final MainActivityLayout mAppRootView;
75    private final ModeListView mModeListView;
76    private final View mFilmStripView;
77    private TextureView mTextureView;
78    private CameraControls mCameraControls;
79    private RenderOverlay mRenderOverlay;
80    private View mFlashOverlay;
81    private ViewGroup mModuleUI;
82
83    private GestureDetector mGestureDetector;
84    private int mSwipeState = IDLE;
85    private ImageView mPreviewThumbView;
86
87    public interface AnimationFinishedListener {
88        public void onAnimationFinished(boolean success);
89    }
90
91    private class MyTouchListener implements View.OnTouchListener {
92
93        @Override
94        public boolean onTouch(View v, MotionEvent event) {
95            return mGestureDetector.onTouchEvent(event);
96        }
97    }
98
99    /**
100     * This gesture listener finds out the direction of the scroll gestures and
101     * sends them to CameraAppUI to do further handling.
102     */
103    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
104        private MotionEvent mDown;
105
106        @Override
107        public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
108            if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT
109                    || mSwipeState != IDLE) {
110                return true;
111            }
112
113            int deltaX = (int) (ev.getX() - mDown.getX());
114            int deltaY = (int) (ev.getY() - mDown.getY());
115            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
116                if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
117                    // Calculate the direction of the swipe.
118                    if (deltaX >= Math.abs(deltaY)) {
119                        // Swipe right.
120                        setSwipeState(SWIPE_RIGHT);
121                    } else if (deltaX <= -Math.abs(deltaY)) {
122                        // Swipe left.
123                        setSwipeState(SWIPE_LEFT);
124                    } else if (deltaY >= Math.abs(deltaX)) {
125                        // Swipe down.
126                        setSwipeState(SWIPE_DOWN);
127                    } else if (deltaY <= -Math.abs(deltaX)) {
128                        // Swipe up.
129                        setSwipeState(SWIPE_UP);
130                    }
131                }
132            }
133            return true;
134        }
135
136        private void setSwipeState(int swipeState) {
137            mSwipeState = swipeState;
138            // Notify new swipe detected.
139            onSwipeDetected(swipeState);
140        }
141
142        @Override
143        public boolean onDown(MotionEvent ev) {
144            mDown = MotionEvent.obtain(ev);
145            mSwipeState = IDLE;
146            return true;
147        }
148
149        @Override
150        public boolean onSingleTapUp(MotionEvent ev) {
151            // This keeps pie menu functioning until the alternative is in.
152            // TODO: Remove after bottom bar is finalized.
153            mRenderOverlay.directTouchEventsToPie(mDown);
154            mRenderOverlay.directTouchEventsToPie(ev);
155            return true;
156        }
157    }
158
159    public CameraAppUI(AppController controller, MainActivityLayout appRootView,
160                       ViewGroup cameraRoot,
161                       boolean isSecureCamera, boolean isCaptureIntent) {
162        mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
163        mController = controller;
164        mIsSecureCamera = isSecureCamera;
165        mIsCaptureIntent = isCaptureIntent;
166
167        mAppRootView = appRootView;
168        mFilmStripView = appRootView.findViewById(R.id.filmstrip_view);
169        mCameraRootView = cameraRoot;
170        mModeTransitionView = (ModeTransitionView)
171                mAppRootView.findViewById(R.id.mode_transition_view);
172        mGestureDetector = new GestureDetector(controller.getAndroidContext(),
173                new MyGestureListener());
174        mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
175        if (mModeListView != null) {
176            mModeListView.setModeSwitchListener(this);
177        } else {
178            Log.e(TAG, "Cannot find mode list in the view hierarchy");
179        }
180        mAnimationManager = new AnimationManager();
181    }
182
183    /**
184     * Redirects touch events to appropriate recipient views based on swipe direction.
185     * More specifically, swipe up and swipe down will be handled by the view that handles
186     * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
187     * to mode list in order to bring up mode list.
188     */
189    private void onSwipeDetected(int swipeState) {
190        if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
191            // Quick switch between photo/video.
192            if (mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO ||
193                    mController.getCurrentModuleIndex() == ModeListView.MODE_VIDEO) {
194                mAppRootView.redirectTouchEventsTo(mModeTransitionView);
195
196                final int moduleToTransitionTo =
197                        mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO ?
198                        ModeListView.MODE_VIDEO : ModeListView.MODE_PHOTO;
199                int shadeColorId = ModeListView.getModeThemeColor(moduleToTransitionTo);
200                int iconRes = ModeListView.getModeIconResourceId(moduleToTransitionTo);
201
202                AnimationFinishedListener listener = new AnimationFinishedListener() {
203                    public void onAnimationFinished(boolean success) {
204                        if (success) {
205                            // Go to new module when the previous operation is successful.
206                            mController.onModeSelected(moduleToTransitionTo);
207                            mModeTransitionView.startPeepHoleAnimation();
208                        }
209                    }
210                };
211                if (mSwipeState == SWIPE_UP) {
212                    mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
213                } else {
214                    mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
215                }
216            }
217        } else if (swipeState == SWIPE_LEFT) {
218            // Pass the touch sequence to filmstrip.
219            mAppRootView.redirectTouchEventsTo(mFilmStripView);
220
221        } else if (swipeState == SWIPE_RIGHT) {
222            // Pass the touch to mode switcher
223            mAppRootView.redirectTouchEventsTo(mModeListView);
224        }
225    }
226
227    /**
228     * This inflates generic_module layout, which contains all the shared views across
229     * modules. Then each module inflates their own views in the given view group. For
230     * now, this is called every time switching from a not-yet-refactored module to a
231     * refactored module. In the future, this should only need to be done once per app
232     * start.
233     */
234    public void prepareModuleUI() {
235        mCameraRootView.removeAllViews();
236        LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
237                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
238        inflater.inflate(R.layout.generic_module, mCameraRootView, true);
239
240        mModuleUI = (ViewGroup) mCameraRootView.findViewById(R.id.module_layout);
241        mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
242        mRenderOverlay = (RenderOverlay) mCameraRootView.findViewById(R.id.render_overlay);
243        mRenderOverlay.setOnTouchListener(new MyTouchListener());
244        mFlashOverlay = mCameraRootView.findViewById(R.id.flash_overlay);
245        mPreviewThumbView = (ImageView) mCameraRootView.findViewById(R.id.preview_thumb);
246
247                // TODO: Remove camera controls.
248        mCameraControls = (CameraControls) mCameraRootView.findViewById(R.id.camera_controls);
249    }
250
251    // TODO: Remove this when refactor is done.
252    // This is here to ensure refactored modules can work with not-yet-refactored ones.
253    public void clearCameraUI() {
254        mCameraRootView.removeAllViews();
255        mModuleUI = null;
256        mTextureView = null;
257        mRenderOverlay = null;
258        mFlashOverlay = null;
259        mCameraControls = null;
260    }
261
262    /**
263     * Called indirectly from each module in their initialization to get a view group
264     * to inflate the module specific views in.
265     *
266     * @return a view group for modules to attach views to
267     */
268    public ViewGroup getModuleRootView() {
269        return mModuleUI;
270    }
271
272    /**
273     * Remove all the module specific views.
274     */
275    public void clearModuleUI() {
276        if (mModuleUI != null) {
277            mModuleUI.removeAllViews();
278        }
279        mRenderOverlay.clear();
280    }
281
282    @Override
283    public void onModeSelected(int modeIndex) {
284        mController.onModeSelected(modeIndex);
285    }
286
287    /**
288     * Sets the transform matrix on the preview TextureView
289     */
290    public void setPreviewTransformMatrix(Matrix transformMatrix) {
291        if (mTextureView == null) {
292            throw new UnsupportedOperationException("Cannot set transform matrix on a null" +
293                    " TextureView");
294        }
295        mTextureView.setTransform(transformMatrix);
296    }
297
298
299    /********************** Capture animation **********************/
300    /* TODO: This session is subject to UX changes. In addition to the generic
301       flash animation and post capture animation, consider designating a parameter
302       for specifying the type of animation, as well as an animation finished listener
303       so that modules can have more knowledge of the status of the animation. */
304
305    /**
306     * Starts the pre-capture animation.
307     */
308    public void startPreCaptureAnimation() {
309        mAnimationManager.startFlashAnimation(mFlashOverlay);
310    }
311
312    /**
313     * Cancels the pre-capture animation.
314     */
315    public void cancelPreCaptureAnimation() {
316        mAnimationManager.cancelAnimations();
317    }
318
319    /**
320     * Starts the post-capture animation with the current preview image.
321     */
322    public void startPostCaptureAnimation() {
323        if (mTextureView == null) {
324            Log.e(TAG, "Cannot get a frame from a null TextureView for animation");
325            return;
326        }
327        // TODO: Down sample bitmap
328        startPostCaptureAnimation(mTextureView.getBitmap());
329    }
330
331    /**
332     * Starts the post-capture animation with the given thumbnail.
333     *
334     * @param thumbnail The thumbnail for the animation.
335     */
336    public void startPostCaptureAnimation(Bitmap thumbnail) {
337        mPreviewThumbView.setImageBitmap(thumbnail);
338        mAnimationManager.startCaptureAnimation(mPreviewThumbView);
339    }
340
341    /**
342     * Cancels the post-capture animation.
343     */
344    public void cancelPostCaptureAnimation() {
345        mAnimationManager.cancelAnimations();
346    }
347}
348