VideoUI.java revision 69ef5ea6ed838cf6fba834811f01122d349a61cf
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;
18
19import android.graphics.Bitmap;
20import android.hardware.Camera.Parameters;
21import android.util.Log;
22import android.view.Gravity;
23import android.view.MotionEvent;
24import android.view.SurfaceHolder;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.view.ViewGroup;
28import android.widget.FrameLayout;
29import android.widget.FrameLayout.LayoutParams;
30import android.widget.ImageView;
31import android.widget.LinearLayout;
32import android.widget.TextView;
33
34import com.android.camera.CameraPreference.OnPreferenceChangedListener;
35import com.android.camera.ui.AbstractSettingPopup;
36import com.android.camera.ui.PieRenderer;
37import com.android.camera.ui.PreviewSurfaceView;
38import com.android.camera.ui.RenderOverlay;
39import com.android.camera.ui.RotateLayout;
40import com.android.camera.ui.ZoomRenderer;
41import com.android.gallery3d.R;
42import com.android.gallery3d.common.ApiHelper;
43
44import java.util.List;
45
46public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener,
47        PreviewGestures.SingleTapListener,
48        PreviewGestures.SwipeListener {
49    private final static String TAG = "CAM_VideoUI";
50    // module fields
51    private CameraActivity mActivity;
52    private View mRootView;
53    private PreviewFrameLayout mPreviewFrameLayout;
54    private boolean mSurfaceViewReady;
55    private PreviewSurfaceView mPreviewSurfaceView;
56    // An review image having same size as preview. It is displayed when
57    // recording is stopped in capture intent.
58    private ImageView mReviewImage;
59    private View mReviewCancelButton;
60    private View mReviewDoneButton;
61    private View mReviewPlayButton;
62    private ShutterButton mShutterButton;
63    private TextView mRecordingTimeView;
64    private LinearLayout mLabelsLinearLayout;
65    private View mTimeLapseLabel;
66    private RenderOverlay mRenderOverlay;
67    private PieRenderer mPieRenderer;
68    private VideoMenu mVideoMenu;
69    private AbstractSettingPopup mPopup;
70    private ZoomRenderer mZoomRenderer;
71    private PreviewGestures mGestures;
72    private View mMenu;
73    private View mBlocker;
74    private OnScreenIndicators mOnScreenIndicators;
75    private RotateLayout mRecordingTimeRect;
76    private VideoController mController;
77    private int mZoomMax;
78    private List<Integer> mZoomRatios;
79
80    public VideoUI(CameraActivity activity, VideoController controller, View parent) {
81        mActivity = activity;
82        mController = controller;
83        mRootView = parent;
84        mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true);
85        mPreviewSurfaceView = (PreviewSurfaceView) mRootView
86                .findViewById(R.id.preview_surface_view);
87        initializeMiscControls();
88        initializeControlByIntent();
89        initializeOverlay();
90    }
91
92    private void initializeControlByIntent() {
93        mBlocker = mActivity.findViewById(R.id.blocker);
94        mMenu = mActivity.findViewById(R.id.menu);
95        mMenu.setOnClickListener(new OnClickListener() {
96            @Override
97            public void onClick(View v) {
98                if (mPieRenderer != null) {
99                    mPieRenderer.showInCenter();
100                }
101            }
102        });
103        mOnScreenIndicators = new OnScreenIndicators(mActivity,
104                mActivity.findViewById(R.id.on_screen_indicators));
105        mOnScreenIndicators.resetToDefault();
106        if (mController.isVideoCaptureIntent()) {
107            mActivity.hideSwitcher();
108            ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
109            mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
110            // Cannot use RotateImageView for "done" and "cancel" button because
111            // the tablet layout uses RotateLayout, which cannot be cast to
112            // RotateImageView.
113            mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
114            mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
115            mReviewPlayButton = mActivity.findViewById(R.id.btn_play);
116            mReviewCancelButton.setVisibility(View.VISIBLE);
117            mReviewDoneButton.setOnClickListener(new OnClickListener() {
118                @Override
119                public void onClick(View v) {
120                    mController.onReviewDoneClicked(v);
121                }
122            });
123            mReviewCancelButton.setOnClickListener(new OnClickListener() {
124                @Override
125                public void onClick(View v) {
126                    mController.onReviewCancelClicked(v);
127                }
128            });
129            mReviewPlayButton.setOnClickListener(new OnClickListener() {
130                @Override
131                public void onClick(View v) {
132                    mController.onReviewPlayClicked(v);
133                }
134            });
135        }
136    }
137
138    public boolean collapseCameraControls() {
139        boolean ret = false;
140        if (mPopup != null) {
141            dismissPopup(false);
142            ret = true;
143        }
144        return ret;
145    }
146
147    public boolean removeTopLevelPopup() {
148        if (mPopup != null) {
149            dismissPopup(true);
150            return true;
151        }
152        return false;
153    }
154
155    public void enableCameraControls(boolean enable) {
156        if (mGestures != null) {
157            mGestures.setZoomOnly(!enable);
158        }
159        if (mPieRenderer != null && mPieRenderer.showsItems()) {
160            mPieRenderer.hide();
161        }
162    }
163
164    public void overrideSettings(final String... keyvalues) {
165        mVideoMenu.overrideSettings(keyvalues);
166    }
167
168    public View getPreview() {
169        return mPreviewFrameLayout;
170    }
171
172    public void setOrientationIndicator(int orientation, boolean animation) {
173        if (mGestures != null) {
174            mGestures.setOrientation(orientation);
175        }
176        // We change the orientation of the linearlayout only for phone UI
177        // because when in portrait the width is not enough.
178        if (mLabelsLinearLayout != null) {
179            if (((orientation / 90) & 1) == 0) {
180                mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
181            } else {
182                mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
183            }
184        }
185        mRecordingTimeRect.setOrientation(0, animation);
186    }
187
188    public SurfaceHolder getSurfaceHolder() {
189        return mPreviewSurfaceView.getHolder();
190    }
191
192    public void hideSurfaceView() {
193        mPreviewSurfaceView.setVisibility(View.GONE);
194    }
195
196    public void showSurfaceView() {
197        mPreviewSurfaceView.setVisibility(View.VISIBLE);
198    }
199
200    private void initializeOverlay() {
201        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
202        if (mPieRenderer == null) {
203            mPieRenderer = new PieRenderer(mActivity);
204            mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer);
205            mPieRenderer.setPieListener(this);
206        }
207        mRenderOverlay.addRenderer(mPieRenderer);
208        if (mZoomRenderer == null) {
209            mZoomRenderer = new ZoomRenderer(mActivity);
210        }
211        mRenderOverlay.addRenderer(mZoomRenderer);
212        if (mGestures == null) {
213            mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this);
214        }
215        mGestures.setRenderOverlay(mRenderOverlay);
216        mGestures.reset();
217        mGestures.addTouchReceiver(mMenu);
218        mGestures.addUnclickableArea(mBlocker);
219        if (mController.isVideoCaptureIntent()) {
220            if (mReviewCancelButton != null) {
221                mGestures.addTouchReceiver(mReviewCancelButton);
222            }
223            if (mReviewDoneButton != null) {
224                mGestures.addTouchReceiver(mReviewDoneButton);
225            }
226            if (mReviewPlayButton != null) {
227                mGestures.addTouchReceiver(mReviewPlayButton);
228            }
229        }
230    }
231
232    public void setPrefChangedListener(OnPreferenceChangedListener listener) {
233        mVideoMenu.setListener(listener);
234    }
235
236    private void initializeMiscControls() {
237        mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
238        mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
239        mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
240        mShutterButton = mActivity.getShutterButton();
241        mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
242        mShutterButton.setOnShutterButtonListener(mController);
243        mShutterButton.setVisibility(View.VISIBLE);
244        mShutterButton.requestFocus();
245        mShutterButton.enableTouch(true);
246        mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time);
247        mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect);
248        mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label);
249        // The R.id.labels can only be found in phone layout.
250        // That is, mLabelsLinearLayout should be null in tablet layout.
251        mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels);
252    }
253
254    public void updateOnScreenIndicators(Parameters param, ComboPreferences prefs) {
255      mOnScreenIndicators.updateFlashOnScreenIndicator(param.getFlashMode());
256      boolean location = RecordLocationPreference.get(
257              prefs, mActivity.getContentResolver());
258      mOnScreenIndicators.updateLocationIndicator(location);
259
260    }
261
262    public void setAspectRatio(double ratio) {
263        mPreviewFrameLayout.setAspectRatio(ratio);
264    }
265
266    public void showTimeLapseUI(boolean enable) {
267        if (mTimeLapseLabel != null) {
268            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
269        }
270    }
271
272    private void openMenu() {
273        if (mPieRenderer != null) {
274            mPieRenderer.showInCenter();
275        }
276    }
277
278    public void showPopup(AbstractSettingPopup popup) {
279        mActivity.hideUI();
280        mBlocker.setVisibility(View.INVISIBLE);
281        setShowMenu(false);
282        mPopup = popup;
283        mPopup.setVisibility(View.VISIBLE);
284        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
285                LayoutParams.WRAP_CONTENT);
286        lp.gravity = Gravity.CENTER;
287        ((FrameLayout) mRootView).addView(mPopup, lp);
288        mGestures.addTouchReceiver(mPopup);
289    }
290
291    public void dismissPopup(boolean topLevelOnly) {
292        dismissPopup(topLevelOnly, true);
293    }
294
295    public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) {
296        // In review mode, we do not want to bring up the camera UI
297        if (mController.isInReviewMode()) return;
298
299        if (fullScreen) {
300            mActivity.showUI();
301            mBlocker.setVisibility(View.VISIBLE);
302        }
303        setShowMenu(fullScreen);
304        if (mPopup != null) {
305            mGestures.removeTouchReceiver(mPopup);
306            ((FrameLayout) mRootView).removeView(mPopup);
307            mPopup = null;
308        }
309        mVideoMenu.popupDismissed(topLevelPopupOnly);
310    }
311
312    public void onShowSwitcherPopup() {
313        hidePieRenderer();
314    }
315
316    public boolean hidePieRenderer() {
317        if (mPieRenderer != null && mPieRenderer.showsItems()) {
318            mPieRenderer.hide();
319            return true;
320        }
321        return false;
322    }
323
324    // disable preview gestures after shutter is pressed
325    public void setShutterPressed(boolean pressed) {
326        if (mGestures == null) return;
327        mGestures.setEnabled(!pressed);
328    }
329
330    public void enableShutter(boolean enable) {
331        if (mShutterButton != null) {
332            mShutterButton.setEnabled(enable);
333        }
334    }
335
336    // PieListener
337    @Override
338    public void onPieOpened(int centerX, int centerY) {
339        mActivity.cancelActivityTouchHandling();
340        mActivity.setSwipingEnabled(false);
341    }
342
343    @Override
344    public void onPieClosed() {
345        mActivity.setSwipingEnabled(true);
346    }
347
348    public void showPreviewBorder(boolean enable) {
349        mPreviewFrameLayout.showBorder(enable);
350    }
351
352    // SingleTapListener
353    // Preview area is touched. Take a picture.
354    @Override
355    public void onSingleTapUp(View view, int x, int y) {
356        mController.onSingleTapUp(view, x, y);
357    }
358
359    // SurfaceView callback
360    @Override
361    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
362        Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
363    }
364
365    @Override
366    public void surfaceCreated(SurfaceHolder holder) {
367        Log.v(TAG, "Surface created");
368        mSurfaceViewReady = true;
369    }
370
371    @Override
372    public void surfaceDestroyed(SurfaceHolder holder) {
373        Log.v(TAG, "Surface destroyed");
374        mSurfaceViewReady = false;
375        mController.stopPreview();
376    }
377
378    public boolean isSurfaceViewReady() {
379        return mSurfaceViewReady;
380    }
381
382    public void showRecordingUI(boolean recording, boolean zoomSupported) {
383        mMenu.setVisibility(recording ? View.GONE : View.VISIBLE);
384        mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE);
385        if (recording) {
386            mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
387            mActivity.hideSwitcher();
388            mRecordingTimeView.setText("");
389            mRecordingTimeView.setVisibility(View.VISIBLE);
390            // The camera is not allowed to be accessed in older api levels during
391            // recording. It is therefore necessary to hide the zoom UI on older
392            // platforms.
393            // See the documentation of android.media.MediaRecorder.start() for
394            // further explanation.
395            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
396                // TODO: disable zoom UI here.
397            }
398        } else {
399            mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
400            mActivity.showSwitcher();
401            mRecordingTimeView.setVisibility(View.GONE);
402            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
403                // TODO: enable zoom UI here.
404            }
405        }
406    }
407
408    public void showReviewImage(Bitmap bitmap) {
409        mReviewImage.setImageBitmap(bitmap);
410        mReviewImage.setVisibility(View.VISIBLE);
411    }
412
413    public void showReviewControls() {
414        Util.fadeOut(mShutterButton);
415        Util.fadeIn(mReviewDoneButton);
416        Util.fadeIn(mReviewPlayButton);
417        mReviewImage.setVisibility(View.VISIBLE);
418        mMenu.setVisibility(View.GONE);
419        mOnScreenIndicators.setVisibility(View.GONE);
420    }
421
422    public void hideReviewUI() {
423        mReviewImage.setVisibility(View.GONE);
424        mShutterButton.setEnabled(true);
425        mMenu.setVisibility(View.VISIBLE);
426        mOnScreenIndicators.setVisibility(View.VISIBLE);
427        Util.fadeOut(mReviewDoneButton);
428        Util.fadeOut(mReviewPlayButton);
429        Util.fadeIn(mShutterButton);
430    }
431
432    private void setShowMenu(boolean show) {
433        if (mOnScreenIndicators != null) {
434            mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
435        }
436        if (mMenu != null) {
437            mMenu.setVisibility(show ? View.VISIBLE : View.GONE);
438        }
439    }
440
441    public void onFullScreenChanged(boolean full) {
442        if (mGestures != null) {
443            mGestures.setEnabled(full);
444        }
445        if (mPopup != null) {
446            dismissPopup(false, full);
447        }
448        if (mRenderOverlay != null) {
449            // this can not happen in capture mode
450            mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
451        }
452        setShowMenu(full);
453        if (mBlocker != null) {
454            // this can not happen in capture mode
455            mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
456        }
457    }
458
459    public void initializePopup(PreferenceGroup pref) {
460        mVideoMenu.initialize(pref);
461    }
462
463    public void initializeZoom(Parameters param) {
464        if (param == null || !param.isZoomSupported()) return;
465        mZoomMax = param.getMaxZoom();
466        mZoomRatios = param.getZoomRatios();
467        // Currently we use immediate zoom for fast zooming to get better UX and
468        // there is no plan to take advantage of the smooth zoom.
469        mZoomRenderer.setZoomMax(mZoomMax);
470        mZoomRenderer.setZoom(param.getZoom());
471        mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom()));
472        mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
473    }
474
475    public void clickShutter() {
476        mShutterButton.performClick();
477    }
478
479    public void pressShutter(boolean pressed) {
480        mShutterButton.setPressed(pressed);
481    }
482
483    public boolean dispatchTouchEvent(MotionEvent m) {
484        if (mGestures != null && mRenderOverlay != null) {
485            return mGestures.dispatchTouch(m);
486        }
487        return false;
488    }
489
490    public void setRecordingTime(String text) {
491        mRecordingTimeView.setText(text);
492    }
493
494    public void setRecordingTimeTextColor(int color) {
495        mRecordingTimeView.setTextColor(color);
496    }
497
498    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
499        @Override
500        public void onZoomValueChanged(int index) {
501            int newZoom = mController.onZoomChanged(index);
502            if (mZoomRenderer != null) {
503                mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
504            }
505        }
506
507        @Override
508        public void onZoomStart() {
509        }
510
511        @Override
512        public void onZoomEnd() {
513        }
514    }
515
516    @Override
517    public void onSwipe(int direction) {
518        if (direction == PreviewGestures.DIR_UP) {
519            openMenu();
520        }
521    }
522
523}
524