blob: 0afcd8a8d5d4bbdd9f0705821c7fefcba5f4ffd5 [file] [log] [blame]
Doris Liu6827ce22013-03-12 19:24:28 -07001/*
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,
Michael Kolb3bc96b22013-03-12 10:24:42 -070047 PreviewGestures.SingleTapListener,
48 PreviewGestures.SwipeListener {
Doris Liu6827ce22013-03-12 19:24:28 -070049 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;
Doris Liu6827ce22013-03-12 19:24:28 -070055 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;
Sascha Haeberling140c2632013-04-23 09:58:51 -070074 private OnScreenIndicators mOnScreenIndicators;
Doris Liu6827ce22013-03-12 19:24:28 -070075 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 });
Michael Kolbeb8adc12013-04-26 11:09:29 -0700103 mOnScreenIndicators = new OnScreenIndicators(mActivity,
Sascha Haeberling140c2632013-04-23 09:58:51 -0700104 mActivity.findViewById(R.id.on_screen_indicators));
105 mOnScreenIndicators.resetToDefault();
Doris Liu6827ce22013-03-12 19:24:28 -0700106 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) {
Michael Kolb3bc96b22013-03-12 10:24:42 -0700213 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this);
Doris Liu6827ce22013-03-12 19:24:28 -0700214 }
215 mGestures.setRenderOverlay(mRenderOverlay);
Doris Liu9cdfe002013-04-16 09:50:56 -0700216 mGestures.reset();
Doris Liu6827ce22013-03-12 19:24:28 -0700217 mGestures.addTouchReceiver(mMenu);
Doris Liu9cdfe002013-04-16 09:50:56 -0700218 mGestures.addUnclickableArea(mBlocker);
Doris Liu6827ce22013-03-12 19:24:28 -0700219 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
Michael Kolb87880792013-04-30 15:38:49 -0700254 public void updateOnScreenIndicators(Parameters param, ComboPreferences prefs) {
Sascha Haeberling140c2632013-04-23 09:58:51 -0700255 mOnScreenIndicators.updateFlashOnScreenIndicator(param.getFlashMode());
Michael Kolb87880792013-04-30 15:38:49 -0700256 boolean location = RecordLocationPreference.get(
257 prefs, mActivity.getContentResolver());
258 mOnScreenIndicators.updateLocationIndicator(location);
259
Doris Liu6827ce22013-03-12 19:24:28 -0700260 }
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
Michael Kolb3bc96b22013-03-12 10:24:42 -0700272 private void openMenu() {
273 if (mPieRenderer != null) {
274 mPieRenderer.showInCenter();
275 }
276 }
277
Doris Liu6827ce22013-03-12 19:24:28 -0700278 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);
Doris Liufd2e6192013-04-23 11:00:37 -0700288 mGestures.addTouchReceiver(mPopup);
Doris Liu6827ce22013-03-12 19:24:28 -0700289 }
290
291 public void dismissPopup(boolean topLevelOnly) {
292 dismissPopup(topLevelOnly, true);
293 }
294
295 public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) {
296 if (fullScreen) {
297 mActivity.showUI();
298 mBlocker.setVisibility(View.VISIBLE);
299 }
300 setShowMenu(fullScreen);
301 if (mPopup != null) {
Doris Liufd2e6192013-04-23 11:00:37 -0700302 mGestures.removeTouchReceiver(mPopup);
Doris Liu6827ce22013-03-12 19:24:28 -0700303 ((FrameLayout) mRootView).removeView(mPopup);
304 mPopup = null;
305 }
306 mVideoMenu.popupDismissed(topLevelPopupOnly);
307 }
308
309 public void onShowSwitcherPopup() {
310 hidePieRenderer();
311 }
312
313 public boolean hidePieRenderer() {
314 if (mPieRenderer != null && mPieRenderer.showsItems()) {
315 mPieRenderer.hide();
316 return true;
317 }
318 return false;
319 }
320
Doris Liu61f2b082013-03-27 17:25:43 -0700321 // disable preview gestures after shutter is pressed
322 public void setShutterPressed(boolean pressed) {
323 if (mGestures == null) return;
324 mGestures.setEnabled(!pressed);
325 }
326
Doris Liu6827ce22013-03-12 19:24:28 -0700327 public void enableShutter(boolean enable) {
328 if (mShutterButton != null) {
329 mShutterButton.setEnabled(enable);
330 }
331 }
332
333 // PieListener
334 @Override
335 public void onPieOpened(int centerX, int centerY) {
336 mActivity.cancelActivityTouchHandling();
337 mActivity.setSwipingEnabled(false);
338 }
339
340 @Override
341 public void onPieClosed() {
342 mActivity.setSwipingEnabled(true);
343 }
344
345 public void showPreviewBorder(boolean enable) {
346 mPreviewFrameLayout.showBorder(enable);
347 }
348
349 // SingleTapListener
350 // Preview area is touched. Take a picture.
351 @Override
352 public void onSingleTapUp(View view, int x, int y) {
353 mController.onSingleTapUp(view, x, y);
354 }
355
356 // SurfaceView callback
357 @Override
358 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
359 Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
360 }
361
362 @Override
363 public void surfaceCreated(SurfaceHolder holder) {
364 Log.v(TAG, "Surface created");
365 mSurfaceViewReady = true;
366 }
367
368 @Override
369 public void surfaceDestroyed(SurfaceHolder holder) {
370 Log.v(TAG, "Surface destroyed");
371 mSurfaceViewReady = false;
372 mController.stopPreview();
373 }
374
375 public boolean isSurfaceViewReady() {
376 return mSurfaceViewReady;
377 }
378
379 public void showRecordingUI(boolean recording, boolean zoomSupported) {
380 mMenu.setVisibility(recording ? View.GONE : View.VISIBLE);
381 mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE);
382 if (recording) {
383 mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
384 mActivity.hideSwitcher();
385 mRecordingTimeView.setText("");
386 mRecordingTimeView.setVisibility(View.VISIBLE);
387 // The camera is not allowed to be accessed in older api levels during
388 // recording. It is therefore necessary to hide the zoom UI on older
389 // platforms.
390 // See the documentation of android.media.MediaRecorder.start() for
391 // further explanation.
392 if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
393 // TODO: disable zoom UI here.
394 }
395 } else {
396 mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
397 mActivity.showSwitcher();
398 mRecordingTimeView.setVisibility(View.GONE);
399 if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
400 // TODO: enable zoom UI here.
401 }
402 }
403 }
404
405 public void showReviewImage(Bitmap bitmap) {
406 mReviewImage.setImageBitmap(bitmap);
407 mReviewImage.setVisibility(View.VISIBLE);
408 }
409
410 public void showReviewControls() {
411 Util.fadeOut(mShutterButton);
412 Util.fadeIn(mReviewDoneButton);
413 Util.fadeIn(mReviewPlayButton);
414 mReviewImage.setVisibility(View.VISIBLE);
415 mMenu.setVisibility(View.GONE);
416 mOnScreenIndicators.setVisibility(View.GONE);
417 }
418
419 public void hideReviewUI() {
420 mReviewImage.setVisibility(View.GONE);
421 mShutterButton.setEnabled(true);
422 mMenu.setVisibility(View.VISIBLE);
423 mOnScreenIndicators.setVisibility(View.VISIBLE);
424 Util.fadeOut(mReviewDoneButton);
425 Util.fadeOut(mReviewPlayButton);
426 Util.fadeIn(mShutterButton);
427 }
428
429 private void setShowMenu(boolean show) {
430 if (mOnScreenIndicators != null) {
431 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
432 }
433 if (mMenu != null) {
434 mMenu.setVisibility(show ? View.VISIBLE : View.GONE);
435 }
436 }
437
438 public void onFullScreenChanged(boolean full) {
439 if (mGestures != null) {
440 mGestures.setEnabled(full);
441 }
442 if (mPopup != null) {
443 dismissPopup(false, full);
444 }
445 if (mRenderOverlay != null) {
446 // this can not happen in capture mode
447 mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
448 }
449 setShowMenu(full);
450 if (mBlocker != null) {
451 // this can not happen in capture mode
452 mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
453 }
454 }
455
456 public void initializePopup(PreferenceGroup pref) {
457 mVideoMenu.initialize(pref);
458 }
459
460 public void initializeZoom(Parameters param) {
461 if (param == null || !param.isZoomSupported()) return;
462 mZoomMax = param.getMaxZoom();
463 mZoomRatios = param.getZoomRatios();
464 // Currently we use immediate zoom for fast zooming to get better UX and
465 // there is no plan to take advantage of the smooth zoom.
466 mZoomRenderer.setZoomMax(mZoomMax);
467 mZoomRenderer.setZoom(param.getZoom());
468 mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom()));
469 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
470 }
471
472 public void clickShutter() {
473 mShutterButton.performClick();
474 }
475
476 public void pressShutter(boolean pressed) {
477 mShutterButton.setPressed(pressed);
478 }
479
480 public boolean dispatchTouchEvent(MotionEvent m) {
Doris Liufd2e6192013-04-23 11:00:37 -0700481 if (mGestures != null && mRenderOverlay != null) {
Doris Liu6827ce22013-03-12 19:24:28 -0700482 return mGestures.dispatchTouch(m);
Doris Liu6827ce22013-03-12 19:24:28 -0700483 }
484 return false;
485 }
486
487 public void setRecordingTime(String text) {
488 mRecordingTimeView.setText(text);
489 }
490
491 public void setRecordingTimeTextColor(int color) {
492 mRecordingTimeView.setTextColor(color);
493 }
494
495 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
496 @Override
497 public void onZoomValueChanged(int index) {
498 int newZoom = mController.onZoomChanged(index);
499 if (mZoomRenderer != null) {
500 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
501 }
502 }
503
504 @Override
505 public void onZoomStart() {
506 }
507
508 @Override
509 public void onZoomEnd() {
510 }
511 }
Michael Kolb3bc96b22013-03-12 10:24:42 -0700512
513 @Override
514 public void onSwipe(int direction) {
515 if (direction == PreviewGestures.DIR_UP) {
516 openMenu();
517 }
518 }
519
Doris Liu6827ce22013-03-12 19:24:28 -0700520}