VideoUI.java revision 773e1c9285bd2d0f03ba87db13e77b4f5197ac4f
1/*
2 * Copyright (C) 2012 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.graphics.Color;
21import android.graphics.Matrix;
22import android.graphics.SurfaceTexture;
23import android.graphics.drawable.ColorDrawable;
24import android.hardware.Camera.Parameters;
25import android.os.Handler;
26import android.os.Message;
27import android.util.Log;
28import android.view.Gravity;
29import android.view.SurfaceHolder;
30import android.view.SurfaceView;
31import android.view.TextureView;
32import android.view.TextureView.SurfaceTextureListener;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.view.View.OnLayoutChangeListener;
36import android.view.ViewGroup;
37import android.widget.FrameLayout.LayoutParams;
38import android.widget.ImageView;
39import android.widget.LinearLayout;
40import android.widget.PopupWindow;
41import android.widget.TextView;
42
43import com.android.camera.CameraPreference.OnPreferenceChangedListener;
44import com.android.camera.ui.AbstractSettingPopup;
45import com.android.camera.ui.PieRenderer;
46import com.android.camera.ui.RenderOverlay;
47import com.android.camera.ui.RotateLayout;
48import com.android.camera.ui.ZoomRenderer;
49import com.android.camera.util.CameraUtil;
50import com.android.camera2.R;
51
52import java.util.List;
53
54public class VideoUI implements PieRenderer.PieListener,
55        PreviewGestures.SingleTapListener,
56        SurfaceTextureListener, SurfaceHolder.Callback {
57    private static final String TAG = "CAM_VideoUI";
58    private static final int UPDATE_TRANSFORM_MATRIX = 1;
59    // module fields
60    private CameraActivity mActivity;
61    private View mRootView;
62    private TextureView mTextureView;
63    // An review image having same size as preview. It is displayed when
64    // recording is stopped in capture intent.
65    private ImageView mReviewImage;
66    private View mReviewCancelButton;
67    private View mReviewDoneButton;
68    private View mReviewPlayButton;
69    private TextView mRecordingTimeView;
70    private LinearLayout mLabelsLinearLayout;
71    private View mTimeLapseLabel;
72    private RenderOverlay mRenderOverlay;
73    private PieRenderer mPieRenderer;
74    private VideoMenu mVideoMenu;
75    private SettingsPopup mPopup;
76    private ZoomRenderer mZoomRenderer;
77    private RotateLayout mRecordingTimeRect;
78    private boolean mRecordingStarted = false;
79    private SurfaceTexture mSurfaceTexture;
80    private VideoController mController;
81    private int mZoomMax;
82    private List<Integer> mZoomRatios;
83    private View mFlashOverlay;
84
85    private View mBottomBar;
86    private View mPreviewCover;
87    private SurfaceView mSurfaceView = null;
88    private int mPreviewWidth = 0;
89    private int mPreviewHeight = 0;
90    private float mSurfaceTextureUncroppedWidth;
91    private float mSurfaceTextureUncroppedHeight;
92    private float mAspectRatio = 4f / 3f;
93    private Matrix mMatrix = null;
94    private final AnimationManager mAnimationManager;
95    private final Handler mHandler = new Handler() {
96        @Override
97        public void handleMessage(Message msg) {
98            switch (msg.what) {
99                case UPDATE_TRANSFORM_MATRIX:
100                    setTransformMatrix(mPreviewWidth, mPreviewHeight);
101                    break;
102                default:
103                    break;
104            }
105        }
106    };
107    private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
108        @Override
109        public void onLayoutChange(View v, int left, int top, int right,
110                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
111            int width = right - left;
112            int height = bottom - top;
113            // Full-screen screennail
114            int w = width;
115            int h = height;
116            if (CameraUtil.getDisplayRotation(mActivity) % 180 != 0) {
117                w = height;
118                h = width;
119            }
120            if (mPreviewWidth != width || mPreviewHeight != height) {
121                mPreviewWidth = width;
122                mPreviewHeight = height;
123                onScreenSizeChanged(width, height, w, h);
124            }
125        }
126    };
127
128    public void showPreviewCover() {
129        mPreviewCover.setVisibility(View.VISIBLE);
130    }
131
132    private class SettingsPopup extends PopupWindow {
133        public SettingsPopup(View popup) {
134            super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
135            setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
136            setOutsideTouchable(true);
137            setFocusable(true);
138            popup.setVisibility(View.VISIBLE);
139            setContentView(popup);
140            showAtLocation(mRootView, Gravity.CENTER, 0, 0);
141        }
142
143        public void dismiss(boolean topLevelOnly) {
144            super.dismiss();
145            popupDismissed();
146            showUI();
147            mVideoMenu.popupDismissed(topLevelOnly);
148
149            // Switch back into fullscreen/lights-out mode after popup
150            // is dimissed.
151            mActivity.setSystemBarsVisibility(false);
152        }
153
154        @Override
155        public void dismiss() {
156            // Called by Framework when touch outside the popup or hit back key
157            dismiss(true);
158        }
159    }
160
161    public VideoUI(CameraActivity activity, VideoController controller, View parent) {
162        mActivity = activity;
163        mController = controller;
164        mRootView = parent;
165        ViewGroup moduleRoot = (ViewGroup) mRootView.findViewById(R.id.module_layout);
166        mActivity.getLayoutInflater().inflate(R.layout.video_module,
167                (ViewGroup) moduleRoot, true);
168
169        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
170        mRenderOverlay.setTapListener(this);
171        mPreviewCover = mRootView.findViewById(R.id.preview_cover);
172        mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
173        mTextureView.setSurfaceTextureListener(this);
174        mTextureView.addOnLayoutChangeListener(mLayoutListener);
175
176        mSurfaceTexture = mTextureView.getSurfaceTexture();
177        if (mSurfaceTexture != null) {
178            setTransformMatrix(mTextureView.getWidth(), mTextureView.getHeight());
179            mPreviewCover.setVisibility(View.GONE);
180        }
181
182        mSurfaceTexture = mTextureView.getSurfaceTexture();
183        if (mSurfaceTexture != null) {
184            mPreviewWidth = mTextureView.getWidth();
185            mPreviewHeight = mTextureView.getHeight();
186            setTransformMatrix(mPreviewWidth, mPreviewHeight);
187            mPreviewCover.setVisibility(View.GONE);
188        }
189        mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
190        initializeMiscControls();
191        initializeControlByIntent();
192        initializeOverlay();
193        mAnimationManager = new AnimationManager();
194
195        mBottomBar = mRootView.findViewById(R.id.bottom_bar);
196        mBottomBar.setBackgroundColor(activity.getResources().getColor(R.color.video_mode_color));
197    }
198
199
200    public void initializeSurfaceView() {
201        mSurfaceView = new SurfaceView(mActivity);
202        ((ViewGroup) mRootView).addView(mSurfaceView, 0);
203        mSurfaceView.getHolder().addCallback(this);
204    }
205
206    private void initializeControlByIntent() {
207        if (mController.isVideoCaptureIntent()) {
208            // Cannot use RotateImageView for "done" and "cancel" button because
209            // the tablet layout uses RotateLayout, which cannot be cast to
210            // RotateImageView.
211            mReviewDoneButton = mRootView.findViewById(R.id.btn_done);
212            mReviewCancelButton = mRootView.findViewById(R.id.btn_cancel);
213            mReviewPlayButton = mRootView.findViewById(R.id.btn_play);
214            mReviewCancelButton.setVisibility(View.VISIBLE);
215            mReviewDoneButton.setOnClickListener(new OnClickListener() {
216                @Override
217                public void onClick(View v) {
218                    mController.onReviewDoneClicked(v);
219                }
220            });
221            mReviewCancelButton.setOnClickListener(new OnClickListener() {
222                @Override
223                public void onClick(View v) {
224                    mController.onReviewCancelClicked(v);
225                }
226            });
227            mReviewPlayButton.setOnClickListener(new OnClickListener() {
228                @Override
229                public void onClick(View v) {
230                    mController.onReviewPlayClicked(v);
231                }
232            });
233        }
234    }
235
236    public void setPreviewSize(int width, int height) {
237        if (width == 0 || height == 0) {
238            Log.w(TAG, "Preview size should not be 0.");
239            return;
240        }
241        if (width > height) {
242            mAspectRatio = (float) width / height;
243        } else {
244            mAspectRatio = (float) height / width;
245        }
246        mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX);
247    }
248
249    public int getPreviewWidth() {
250        return mPreviewWidth;
251    }
252
253    public int getPreviewHeight() {
254        return mPreviewHeight;
255    }
256
257    public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
258        setTransformMatrix(width, height);
259    }
260
261    private void setTransformMatrix(int width, int height) {
262        mMatrix = mTextureView.getTransform(mMatrix);
263        int orientation = CameraUtil.getDisplayRotation(mActivity);
264        float scaleX = 1f, scaleY = 1f;
265        float scaledTextureWidth, scaledTextureHeight;
266        if (width > height) {
267            scaledTextureWidth = Math.max(width,
268                    (int) (height * mAspectRatio));
269            scaledTextureHeight = Math.max(height,
270                    (int)(width / mAspectRatio));
271        } else {
272            scaledTextureWidth = Math.max(width,
273                    (int) (height / mAspectRatio));
274            scaledTextureHeight = Math.max(height,
275                    (int) (width * mAspectRatio));
276        }
277
278        if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
279                mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
280            mSurfaceTextureUncroppedWidth = scaledTextureWidth;
281            mSurfaceTextureUncroppedHeight = scaledTextureHeight;
282        }
283        scaleX = scaledTextureWidth / width;
284        scaleY = scaledTextureHeight / height;
285        mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
286        mTextureView.setTransform(mMatrix);
287
288        if (mSurfaceView != null && mSurfaceView.getVisibility() == View.VISIBLE) {
289            LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams();
290            lp.width = (int) mSurfaceTextureUncroppedWidth;
291            lp.height = (int) mSurfaceTextureUncroppedHeight;
292            lp.gravity = Gravity.CENTER;
293            mSurfaceView.requestLayout();
294        }
295    }
296
297    /**
298     * Starts a flash animation
299     */
300    public void animateFlash() {
301        mAnimationManager.startFlashAnimation(mFlashOverlay);
302    }
303
304    /**
305     * Starts a capture animation
306     */
307    public void animateCapture() {
308        Bitmap bitmap = null;
309        if (mTextureView != null) {
310            bitmap = mTextureView.getBitmap((int) mSurfaceTextureUncroppedWidth / 2,
311                    (int) mSurfaceTextureUncroppedHeight / 2);
312        }
313        animateCapture(bitmap);
314    }
315
316    /**
317     * Starts a capture animation
318     * @param bitmap the captured image that we shrink and slide in the animation
319     */
320    public void animateCapture(Bitmap bitmap) {
321        if (bitmap == null) {
322            Log.e(TAG, "No valid bitmap for capture animation.");
323            return;
324        }
325    }
326
327    /**
328     * Cancels on-going animations
329     */
330    public void cancelAnimations() {
331        mAnimationManager.cancelAnimations();
332    }
333
334    public void hideUI() {
335
336    }
337
338    public void showUI() {
339
340    }
341
342    public boolean arePreviewControlsVisible() {
343        return false;
344    }
345
346    public boolean collapseCameraControls() {
347        boolean ret = false;
348        if (mPopup != null) {
349            dismissPopup(false);
350            ret = true;
351        }
352        return ret;
353    }
354
355    public boolean removeTopLevelPopup() {
356        if (mPopup != null) {
357            dismissPopup(true);
358            return true;
359        }
360        return false;
361    }
362
363    public void enableCameraControls(boolean enable) {
364        if (mPieRenderer != null && mPieRenderer.showsItems()) {
365            mPieRenderer.hide();
366        }
367    }
368
369    public void overrideSettings(final String... keyvalues) {
370        mVideoMenu.overrideSettings(keyvalues);
371    }
372
373    public void setOrientationIndicator(int orientation, boolean animation) {
374        // We change the orientation of the linearlayout only for phone UI
375        // because when in portrait the width is not enough.
376        if (mLabelsLinearLayout != null) {
377            if (((orientation / 90) & 1) == 0) {
378                mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
379            } else {
380                mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
381            }
382        }
383        mRecordingTimeRect.setOrientation(0, animation);
384    }
385
386    public SurfaceHolder getSurfaceHolder() {
387        return mSurfaceView.getHolder();
388    }
389
390    public void hideSurfaceView() {
391        mSurfaceView.setVisibility(View.GONE);
392        mTextureView.setVisibility(View.VISIBLE);
393        setTransformMatrix(mPreviewWidth, mPreviewHeight);
394    }
395
396    public void showSurfaceView() {
397        mSurfaceView.setVisibility(View.VISIBLE);
398        mTextureView.setVisibility(View.GONE);
399        setTransformMatrix(mPreviewWidth, mPreviewHeight);
400    }
401
402    private void initializeOverlay() {
403        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
404        if (mPieRenderer == null) {
405            mPieRenderer = new PieRenderer(mActivity);
406            mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer);
407            mPieRenderer.setPieListener(this);
408        }
409        mRenderOverlay.addRenderer(mPieRenderer);
410        if (mZoomRenderer == null) {
411            mZoomRenderer = new ZoomRenderer(mActivity);
412        }
413        mRenderOverlay.addRenderer(mZoomRenderer);
414        mRenderOverlay.setGestures(null);
415    }
416
417    public void setPrefChangedListener(OnPreferenceChangedListener listener) {
418        mVideoMenu.setListener(listener);
419    }
420
421    private void initializeMiscControls() {
422        mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
423        mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time);
424        mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect);
425        mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label);
426        // The R.id.labels can only be found in phone layout.
427        // That is, mLabelsLinearLayout should be null in tablet layout.
428        mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels);
429    }
430
431    public void updateOnScreenIndicators(Parameters param, ComboPreferences prefs) {
432
433    }
434
435    public void setAspectRatio(double ratio) {
436      //  mPreviewFrameLayout.setAspectRatio(ratio);
437    }
438
439    public void showTimeLapseUI(boolean enable) {
440        if (mTimeLapseLabel != null) {
441            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
442        }
443    }
444
445    private void openMenu() {
446        if (mPieRenderer != null) {
447            mPieRenderer.showInCenter();
448        }
449    }
450
451    public void dismissPopup(boolean topLevelOnly) {
452        // In review mode, we do not want to bring up the camera UI
453        if (mController.isInReviewMode()) return;
454        if (mPopup != null) {
455            mPopup.dismiss(topLevelOnly);
456        }
457    }
458
459    private void popupDismissed() {
460        mPopup = null;
461    }
462
463    public void showPopup(AbstractSettingPopup popup) {
464        hideUI();
465
466        if (mPopup != null) {
467            mPopup.dismiss(false);
468        }
469        mPopup = new SettingsPopup(popup);
470    }
471
472    public void onShowSwitcherPopup() {
473        hidePieRenderer();
474    }
475
476    public boolean hidePieRenderer() {
477        if (mPieRenderer != null && mPieRenderer.showsItems()) {
478            mPieRenderer.hide();
479            return true;
480        }
481        return false;
482    }
483
484    public void enableShutter(boolean enable) {
485
486    }
487
488    // PieListener
489    @Override
490    public void onPieOpened(int centerX, int centerY) {
491        setSwipingEnabled(false);
492    }
493
494    @Override
495    public void onPieClosed() {
496        setSwipingEnabled(true);
497    }
498
499    public void setSwipingEnabled(boolean enable) {
500        mActivity.setSwipingEnabled(enable);
501    }
502
503    public void showPreviewBorder(boolean enable) {
504       // TODO: mPreviewFrameLayout.showBorder(enable);
505    }
506
507    // SingleTapListener
508    // Preview area is touched. Take a picture.
509    @Override
510    public void onSingleTapUp(View view, int x, int y) {
511        mController.onSingleTapUp(view, x, y);
512    }
513
514    public void showRecordingUI(boolean recording) {
515        mRecordingStarted = recording;
516        if (recording) {
517            mRecordingTimeView.setText("");
518            mRecordingTimeView.setVisibility(View.VISIBLE);
519        } else {
520            mRecordingTimeView.setVisibility(View.GONE);
521        }
522    }
523
524    public void showReviewImage(Bitmap bitmap) {
525        mReviewImage.setImageBitmap(bitmap);
526        mReviewImage.setVisibility(View.VISIBLE);
527    }
528
529    public void showReviewControls() {
530        CameraUtil.fadeIn(mReviewDoneButton);
531        CameraUtil.fadeIn(mReviewPlayButton);
532        mReviewImage.setVisibility(View.VISIBLE);
533    }
534
535    public void hideReviewUI() {
536        mReviewImage.setVisibility(View.GONE);
537        CameraUtil.fadeOut(mReviewDoneButton);
538        CameraUtil.fadeOut(mReviewPlayButton);
539    }
540
541    private void setShowMenu(boolean show) {
542
543    }
544
545    public void onPreviewFocusChanged(boolean previewFocused) {
546        if (previewFocused) {
547            showUI();
548        } else {
549            hideUI();
550        }
551
552        if (mRenderOverlay != null) {
553            // this can not happen in capture mode
554            mRenderOverlay.setVisibility(previewFocused ? View.VISIBLE : View.GONE);
555        }
556        setShowMenu(previewFocused);
557    }
558
559    public void initializePopup(PreferenceGroup pref) {
560        mVideoMenu.initialize(pref);
561    }
562
563    public void initializeZoom(Parameters param) {
564        mZoomMax = param.getMaxZoom();
565        mZoomRatios = param.getZoomRatios();
566        // Currently we use immediate zoom for fast zooming to get better UX and
567        // there is no plan to take advantage of the smooth zoom.
568        mZoomRenderer.setZoomMax(mZoomMax);
569        mZoomRenderer.setZoom(param.getZoom());
570        mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom()));
571        mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
572    }
573
574    public void clickShutter() {
575
576    }
577
578    public void pressShutter(boolean pressed) {
579
580    }
581
582    public View getShutterButton() {
583        return null;
584    }
585
586    public void setRecordingTime(String text) {
587        mRecordingTimeView.setText(text);
588    }
589
590    public void setRecordingTimeTextColor(int color) {
591        mRecordingTimeView.setTextColor(color);
592    }
593
594    public boolean isVisible() {
595        return false;
596    }
597
598    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
599        @Override
600        public void onZoomValueChanged(int index) {
601            int newZoom = mController.onZoomChanged(index);
602            if (mZoomRenderer != null) {
603                mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
604            }
605        }
606
607        @Override
608        public void onZoomStart() {
609        }
610
611        @Override
612        public void onZoomEnd() {
613        }
614    }
615
616    public SurfaceTexture getSurfaceTexture() {
617        return mSurfaceTexture;
618    }
619
620    // SurfaceTexture callbacks
621    @Override
622    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
623        mSurfaceTexture = surface;
624        mController.onPreviewUIReady();
625    }
626
627    @Override
628    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
629        mSurfaceTexture = null;
630        mController.onPreviewUIDestroyed();
631        Log.d(TAG, "surfaceTexture is destroyed");
632        return true;
633    }
634
635    @Override
636    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
637    }
638
639    @Override
640    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
641        // Make sure preview cover is hidden if preview data is available.
642        if (mPreviewCover.getVisibility() != View.GONE) {
643            mPreviewCover.setVisibility(View.GONE);
644        }
645    }
646
647    // SurfaceHolder callbacks
648    @Override
649    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
650        Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
651    }
652
653    @Override
654    public void surfaceCreated(SurfaceHolder holder) {
655        Log.v(TAG, "Surface created");
656    }
657
658    @Override
659    public void surfaceDestroyed(SurfaceHolder holder) {
660        Log.v(TAG, "Surface destroyed");
661        mController.stopPreview();
662    }
663}
664