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