2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.camera;
20 import android.hardware.Camera;
21 import android.hardware.Camera.Face;
22 import android.hardware.Camera.FaceDetectionListener;
23 import android.util.Log;
24 import android.view.Gravity;
25 import android.view.MotionEvent;
26 import android.view.SurfaceHolder;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.View.OnLayoutChangeListener;
30 import android.view.ViewGroup;
31 import android.view.ViewStub;
32 import android.widget.FrameLayout;
33 import android.widget.FrameLayout.LayoutParams;
34 import android.widget.Toast;
36 import com.android.camera.CameraPreference.OnPreferenceChangedListener;
37 import com.android.camera.FocusOverlayManager.FocusUI;
38 import com.android.camera.ui.AbstractSettingPopup;
39 import com.android.camera.ui.CountDownView;
40 import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
41 import com.android.camera.ui.FaceView;
42 import com.android.camera.ui.FocusIndicator;
43 import com.android.camera.ui.PieRenderer;
44 import com.android.camera.ui.PieRenderer.PieListener;
45 import com.android.camera.ui.RenderOverlay;
46 import com.android.camera.ui.ZoomRenderer;
47 import com.android.gallery3d.R;
48 import com.android.gallery3d.common.ApiHelper;
50 import java.util.List;
52 public class PhotoUI implements PieListener,
53 SurfaceHolder.Callback,
54 PreviewGestures.SingleTapListener,
56 LocationManager.Listener,
57 FaceDetectionListener,
58 PreviewGestures.SwipeListener {
60 private static final String TAG = "CAM_UI";
62 private CameraActivity mActivity;
63 private PhotoController mController;
64 private PreviewGestures mGestures;
66 private View mRootView;
67 private Object mSurfaceTexture;
68 private volatile SurfaceHolder mSurfaceHolder;
70 private AbstractSettingPopup mPopup;
71 private ShutterButton mShutterButton;
72 private CountDownView mCountDownView;
74 private FaceView mFaceView;
75 private RenderOverlay mRenderOverlay;
76 private View mReviewCancelButton;
77 private View mReviewDoneButton;
78 private View mReviewRetakeButton;
80 private View mMenuButton;
81 private View mBlocker;
82 private PhotoMenu mMenu;
84 private OnScreenIndicators mOnScreenIndicators;
86 private PieRenderer mPieRenderer;
87 private ZoomRenderer mZoomRenderer;
88 private Toast mNotSelectableToast;
91 private List<Integer> mZoomRatios;
93 private int mPreviewWidth = 0;
94 private int mPreviewHeight = 0;
96 private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
98 public void onLayoutChange(View v, int left, int top, int right,
99 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
100 int width = right - left;
101 int height = bottom - top;
102 // Full-screen screennail
105 if (Util.getDisplayRotation(mActivity) % 180 != 0) {
109 if (mPreviewWidth != w || mPreviewHeight != h) {
112 mController.onScreenSizeChanged(width, height, w, h);
117 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
118 mActivity = activity;
119 mController = controller;
122 mActivity.getLayoutInflater().inflate(R.layout.photo_module,
123 (ViewGroup) mRootView, true);
124 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
127 mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
128 mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
130 if (ApiHelper.HAS_FACE_DETECTION) {
131 ViewStub faceViewStub = (ViewStub) mRootView
132 .findViewById(R.id.face_view_stub);
133 if (faceViewStub != null) {
134 faceViewStub.inflate();
135 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
141 public View getRootView() {
145 private void initIndicators() {
146 mOnScreenIndicators = new OnScreenIndicators(mActivity,
147 mActivity.findViewById(R.id.on_screen_indicators));
150 public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
151 Camera.Parameters params, OnPreferenceChangedListener listener) {
152 if (mPieRenderer == null) {
153 mPieRenderer = new PieRenderer(mActivity);
154 mPieRenderer.setPieListener(this);
155 mRenderOverlay.addRenderer(mPieRenderer);
158 mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
159 mMenu.setListener(listener);
161 mMenu.initialize(prefGroup);
163 if (mZoomRenderer == null) {
164 mZoomRenderer = new ZoomRenderer(mActivity);
165 mRenderOverlay.addRenderer(mZoomRenderer);
167 if (mGestures == null) {
168 // this will handle gesture disambiguation and dispatching
169 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
173 mGestures.setRenderOverlay(mRenderOverlay);
174 mGestures.addTouchReceiver(mMenuButton);
175 mGestures.addUnclickableArea(mBlocker);
176 // make sure to add touch targets for image capture
177 if (mController.isImageCaptureIntent()) {
178 if (mReviewCancelButton != null) {
179 mGestures.addTouchReceiver(mReviewCancelButton);
181 if (mReviewDoneButton != null) {
182 mGestures.addTouchReceiver(mReviewDoneButton);
185 mRenderOverlay.requestLayout();
187 initializeZoom(params);
188 updateOnScreenIndicators(params, prefGroup, prefs);
191 private void openMenu() {
192 if (mPieRenderer != null) {
193 // If autofocus is not finished, cancel autofocus so that the
194 // subsequent touch can be handled by PreviewGestures
195 if (mController.getCameraState() == PhotoController.FOCUSING) {
196 mController.cancelAutoFocus();
198 mPieRenderer.showInCenter();
202 public void initializeControlByIntent() {
203 mBlocker = mActivity.findViewById(R.id.blocker);
204 mMenuButton = mActivity.findViewById(R.id.menu);
205 mMenuButton.setOnClickListener(new OnClickListener() {
207 public void onClick(View v) {
211 if (mController.isImageCaptureIntent()) {
212 mActivity.hideSwitcher();
213 ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
214 mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
216 mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
217 mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
218 mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
219 mReviewCancelButton.setVisibility(View.VISIBLE);
221 mReviewDoneButton.setOnClickListener(new OnClickListener() {
223 public void onClick(View v) {
224 mController.onCaptureDone();
227 mReviewCancelButton.setOnClickListener(new OnClickListener() {
229 public void onClick(View v) {
230 mController.onCaptureCancelled();
234 mReviewRetakeButton.setOnClickListener(new OnClickListener() {
236 public void onClick(View v) {
237 mController.onCaptureRetake();
243 // called from onResume but only the first time
244 public void initializeFirstTime() {
245 // Initialize shutter button.
246 mShutterButton = mActivity.getShutterButton();
247 mShutterButton.setImageResource(R.drawable.btn_new_shutter);
248 mShutterButton.setOnShutterButtonListener(mController);
249 mShutterButton.setVisibility(View.VISIBLE);
250 mRootView.addOnLayoutChangeListener(mLayoutListener);
253 // called from onResume every other time
254 public void initializeSecondTime(Camera.Parameters params) {
255 initializeZoom(params);
256 if (mController.isImageCaptureIntent()) {
257 hidePostCaptureAlert();
260 mMenu.reloadPreferences();
262 mRootView.addOnLayoutChangeListener(mLayoutListener);
265 public void initializeZoom(Camera.Parameters params) {
266 if ((params == null) || !params.isZoomSupported()
267 || (mZoomRenderer == null)) return;
268 mZoomMax = params.getMaxZoom();
269 mZoomRatios = params.getZoomRatios();
270 // Currently we use immediate zoom for fast zooming to get better UX and
271 // there is no plan to take advantage of the smooth zoom.
272 if (mZoomRenderer != null) {
273 mZoomRenderer.setZoomMax(mZoomMax);
274 mZoomRenderer.setZoom(params.getZoom());
275 mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
276 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
280 public void enableGestures(boolean enable) {
281 if (mGestures != null) {
282 mGestures.setEnabled(enable);
287 public void showGpsOnScreenIndicator(boolean hasSignal) { }
290 public void hideGpsOnScreenIndicator() { }
292 public void overrideSettings(final String ... keyvalues) {
293 mMenu.overrideSettings(keyvalues);
296 public void updateOnScreenIndicators(Camera.Parameters params,
297 PreferenceGroup group, ComboPreferences prefs) {
298 if (params == null) return;
299 mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode());
300 mOnScreenIndicators.updateExposureOnScreenIndicator(params,
301 CameraSettings.readExposure(prefs));
302 mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode());
304 ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE);
306 wbIndex = pref.getCurrentIndex();
308 mOnScreenIndicators.updateWBIndicator(wbIndex);
309 pref = group.findPreference(CameraSettings.KEY_TIMER);
310 boolean timer = false;
312 timer = !(pref.getCurrentIndex() == 0);
314 mOnScreenIndicators.updateTimerIndicator(timer);
315 boolean location = false;
316 pref = group.findPreference(CameraSettings.KEY_RECORD_LOCATION);
318 location = !(pref.getCurrentIndex() == 0);
320 mOnScreenIndicators.updateLocationIndicator(location);
325 public void setCameraState(int state) {
328 public boolean dispatchTouchEvent(MotionEvent m) {
329 if (mGestures != null && mRenderOverlay != null) {
330 return mGestures.dispatchTouch(m);
335 public boolean onBackPressed() {
336 if (mPieRenderer != null && mPieRenderer.showsItems()) {
340 // In image capture mode, back button should:
341 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
343 if (mController.isImageCaptureIntent()) {
344 if (!removeTopLevelPopup()) {
345 // no popup to dismiss, cancel image capture
346 mController.onCaptureCancelled();
349 } else if (!mController.isCameraIdle()) {
350 // ignore backs while we're taking a picture
353 return removeTopLevelPopup();
357 public void onFullScreenChanged(boolean full) {
358 if (mFaceView != null) {
359 mFaceView.setBlockDraw(!full);
361 if (mPopup != null) {
362 dismissPopup(false, full);
364 if (mGestures != null) {
365 mGestures.setEnabled(full);
367 if (mRenderOverlay != null) {
368 // this can not happen in capture mode
369 mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
371 if (mPieRenderer != null) {
372 mPieRenderer.setBlockFocus(!full);
375 if (mBlocker != null) {
376 mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
378 if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
381 public boolean removeTopLevelPopup() {
382 // Remove the top level popup or dialog box and return true if there's any
383 if (mPopup != null) {
390 public void showPopup(AbstractSettingPopup popup) {
392 mBlocker.setVisibility(View.INVISIBLE);
395 mPopup.setVisibility(View.VISIBLE);
396 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
397 LayoutParams.WRAP_CONTENT);
398 lp.gravity = Gravity.CENTER;
399 ((FrameLayout) mRootView).addView(mPopup, lp);
400 mGestures.addTouchReceiver(mPopup);
403 public void dismissPopup(boolean topPopupOnly) {
404 dismissPopup(topPopupOnly, true);
407 private void dismissPopup(boolean topOnly, boolean fullScreen) {
410 mBlocker.setVisibility(View.VISIBLE);
412 setShowMenu(fullScreen);
413 if (mPopup != null) {
414 mGestures.removeTouchReceiver(mPopup);
415 ((FrameLayout) mRootView).removeView(mPopup);
418 mMenu.popupDismissed(topOnly);
421 public void onShowSwitcherPopup() {
422 if (mPieRenderer != null && mPieRenderer.showsItems()) {
427 private void setShowMenu(boolean show) {
428 if (mOnScreenIndicators != null) {
429 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
431 if (mMenuButton != null) {
432 mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
436 public boolean collapseCameraControls() {
437 // Remove all the popups/dialog boxes
439 if (mPopup != null) {
446 protected void showPostCaptureAlert() {
447 mOnScreenIndicators.setVisibility(View.GONE);
448 mMenuButton.setVisibility(View.GONE);
449 Util.fadeIn(mReviewDoneButton);
450 mShutterButton.setVisibility(View.INVISIBLE);
451 Util.fadeIn(mReviewRetakeButton);
454 protected void hidePostCaptureAlert() {
455 mOnScreenIndicators.setVisibility(View.VISIBLE);
456 mMenuButton.setVisibility(View.VISIBLE);
457 Util.fadeOut(mReviewDoneButton);
458 mShutterButton.setVisibility(View.VISIBLE);
459 Util.fadeOut(mReviewRetakeButton);
462 public void setDisplayOrientation(int orientation) {
463 if (mFaceView != null) {
464 mFaceView.setDisplayOrientation(orientation);
468 // shutter button handling
470 public boolean isShutterPressed() {
471 return mShutterButton.isPressed();
477 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
479 public void onZoomValueChanged(int index) {
480 int newZoom = mController.onZoomChanged(index);
481 if (mZoomRenderer != null) {
482 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
487 public void onZoomStart() {
488 if (mPieRenderer != null) {
489 mPieRenderer.setBlockFocus(true);
494 public void onZoomEnd() {
495 if (mPieRenderer != null) {
496 mPieRenderer.setBlockFocus(false);
502 public void onPieOpened(int centerX, int centerY) {
503 mActivity.cancelActivityTouchHandling();
504 mActivity.setSwipingEnabled(false);
505 if (mFaceView != null) {
506 mFaceView.setBlockDraw(true);
511 public void onPieClosed() {
512 mActivity.setSwipingEnabled(true);
513 if (mFaceView != null) {
514 mFaceView.setBlockDraw(false);
521 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
522 Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
527 public void surfaceCreated(SurfaceHolder holder) {
528 Log.v(TAG, "surfaceCreated: " + holder);
529 mSurfaceHolder = holder;
530 mController.onSurfaceCreated(holder);
534 public void surfaceDestroyed(SurfaceHolder holder) {
535 Log.v(TAG, "surfaceDestroyed: " + holder);
536 mSurfaceHolder = null;
537 mController.stopPreview();
540 public Object getSurfaceTexture() {
541 return mSurfaceTexture;
544 public void setSurfaceTexture(Object st) {
545 mSurfaceTexture = st;
548 public SurfaceHolder getSurfaceHolder() {
549 return mSurfaceHolder;
552 public boolean isCountingDown() {
553 return mCountDownView.isCountingDown();
556 public void cancelCountDown() {
557 mCountDownView.cancelCountDown();
560 public void startCountDown(int sec, boolean playSound) {
561 mCountDownView.startCountDown(sec, playSound);
564 public void showPreferencesToast() {
565 if (mNotSelectableToast == null) {
566 String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
567 mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
569 mNotSelectableToast.show();
572 public void onPause() {
573 mCountDownView.cancelCountDown();
574 // Close the camera now because other activities may need to use it.
575 mSurfaceTexture = null;
578 collapseCameraControls();
579 if (mFaceView != null) mFaceView.clear();
582 mRootView.removeOnLayoutChangeListener(mLayoutListener);
587 public void enableShutter(boolean enabled) {
588 if (mShutterButton != null) {
589 mShutterButton.setEnabled(enabled);
593 public void pressShutterButton() {
594 if (mShutterButton.isInTouchMode()) {
595 mShutterButton.requestFocusFromTouch();
597 mShutterButton.requestFocus();
599 mShutterButton.setPressed(true);
602 // forward from preview gestures to controller
604 public void onSingleTapUp(View view, int x, int y) {
605 mController.onSingleTapUp(view, x, y);
608 // focus UI implementation
610 private FocusIndicator getFocusIndicator() {
611 return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
615 public boolean hasFaces() {
616 return (mFaceView != null && mFaceView.faceExists());
619 public void clearFaces() {
620 if (mFaceView != null) mFaceView.clear();
624 public void clearFocus() {
625 FocusIndicator indicator = getFocusIndicator();
626 if (indicator != null) indicator.clear();
630 public void setFocusPosition(int x, int y) {
631 mPieRenderer.setFocus(x, y);
635 public void onFocusStarted() {
636 getFocusIndicator().showStart();
640 public void onFocusSucceeded(boolean timeout) {
641 getFocusIndicator().showSuccess(timeout);
645 public void onFocusFailed(boolean timeout) {
646 getFocusIndicator().showFail(timeout);
650 public void pauseFaceDetection() {
651 if (mFaceView != null) mFaceView.pause();
655 public void resumeFaceDetection() {
656 if (mFaceView != null) mFaceView.resume();
659 public void onStartFaceDetection(int orientation, boolean mirror) {
661 mFaceView.setVisibility(View.VISIBLE);
662 mFaceView.setDisplayOrientation(orientation);
663 mFaceView.setMirror(mirror);
668 public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
669 mFaceView.setFaces(faces);
673 public void onSwipe(int direction) {
674 if (direction == PreviewGestures.DIR_UP) {