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.hardware.Camera.Parameters;
24 import android.util.Log;
25 import android.view.Gravity;
26 import android.view.MotionEvent;
27 import android.view.SurfaceHolder;
28 import android.view.View;
29 import android.view.View.OnClickListener;
30 import android.view.View.OnLayoutChangeListener;
31 import android.view.ViewGroup;
32 import android.view.ViewStub;
33 import android.widget.FrameLayout;
34 import android.widget.FrameLayout.LayoutParams;
35 import android.widget.ImageView;
36 import android.widget.Toast;
38 import com.android.camera.CameraPreference.OnPreferenceChangedListener;
39 import com.android.camera.FocusOverlayManager.FocusUI;
40 import com.android.camera.ui.AbstractSettingPopup;
41 import com.android.camera.ui.CountDownView;
42 import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
43 import com.android.camera.ui.FaceView;
44 import com.android.camera.ui.FocusIndicator;
45 import com.android.camera.ui.PieRenderer;
46 import com.android.camera.ui.PieRenderer.PieListener;
47 import com.android.camera.ui.RenderOverlay;
48 import com.android.camera.ui.ZoomRenderer;
49 import com.android.gallery3d.R;
50 import com.android.gallery3d.common.ApiHelper;
52 import java.util.List;
54 public class PhotoUI implements PieListener,
55 SurfaceHolder.Callback,
56 PreviewGestures.SingleTapListener,
58 LocationManager.Listener,
59 FaceDetectionListener,
60 PreviewGestures.SwipeListener {
62 private static final String TAG = "CAM_UI";
64 private CameraActivity mActivity;
65 private PhotoController mController;
66 private PreviewGestures mGestures;
68 private View mRootView;
69 private Object mSurfaceTexture;
70 private volatile SurfaceHolder mSurfaceHolder;
72 private AbstractSettingPopup mPopup;
73 private ShutterButton mShutterButton;
74 private CountDownView mCountDownView;
76 private FaceView mFaceView;
77 private RenderOverlay mRenderOverlay;
78 private View mReviewCancelButton;
79 private View mReviewDoneButton;
80 private View mReviewRetakeButton;
82 private View mMenuButton;
83 private View mBlocker;
84 private PhotoMenu mMenu;
86 // Small indicators which show the camera settings in the viewfinder.
87 private ImageView mExposureIndicator;
88 private ImageView mFlashIndicator;
89 private ImageView mSceneIndicator;
90 private ImageView mHdrIndicator;
91 // A view group that contains all the small indicators.
92 private View mOnScreenIndicators;
94 private PieRenderer mPieRenderer;
95 private ZoomRenderer mZoomRenderer;
96 private Toast mNotSelectableToast;
99 private List<Integer> mZoomRatios;
101 private int mPreviewWidth = 0;
102 private int mPreviewHeight = 0;
104 private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
106 public void onLayoutChange(View v, int left, int top, int right,
107 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
108 int width = right - left;
109 int height = bottom - top;
110 // Full-screen screennail
113 if (Util.getDisplayRotation(mActivity) % 180 != 0) {
117 if (mPreviewWidth != w || mPreviewHeight != h) {
120 mController.onScreenSizeChanged(width, height, w, h);
125 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
126 mActivity = activity;
127 mController = controller;
130 mActivity.getLayoutInflater().inflate(R.layout.photo_module,
131 (ViewGroup) mRootView, true);
132 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
135 mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
136 mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
138 if (ApiHelper.HAS_FACE_DETECTION) {
139 ViewStub faceViewStub = (ViewStub) mRootView
140 .findViewById(R.id.face_view_stub);
141 if (faceViewStub != null) {
142 faceViewStub.inflate();
143 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
147 mRootView.addOnLayoutChangeListener(mLayoutListener);
150 public View getRootView() {
154 private void initIndicators() {
155 mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
156 mExposureIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_exposure_indicator);
157 mFlashIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_flash_indicator);
158 mSceneIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_scenemode_indicator);
159 mHdrIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_hdr_indicator);
162 public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
163 Camera.Parameters params, OnPreferenceChangedListener listener) {
164 if (mPieRenderer == null) {
165 mPieRenderer = new PieRenderer(mActivity);
166 mPieRenderer.setPieListener(this);
167 mRenderOverlay.addRenderer(mPieRenderer);
170 mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
171 mMenu.setListener(listener);
173 mMenu.initialize(prefGroup);
175 if (mZoomRenderer == null) {
176 mZoomRenderer = new ZoomRenderer(mActivity);
177 mRenderOverlay.addRenderer(mZoomRenderer);
179 if (mGestures == null) {
180 // this will handle gesture disambiguation and dispatching
181 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
184 mGestures.clearTouchReceivers();
185 mGestures.setRenderOverlay(mRenderOverlay);
186 mGestures.addTouchReceiver(mMenuButton);
187 mGestures.addTouchReceiver(mBlocker);
188 // make sure to add touch targets for image capture
189 if (mController.isImageCaptureIntent()) {
190 if (mReviewCancelButton != null) {
191 mGestures.addTouchReceiver(mReviewCancelButton);
193 if (mReviewDoneButton != null) {
194 mGestures.addTouchReceiver(mReviewDoneButton);
197 mRenderOverlay.requestLayout();
199 initializeZoom(params);
200 updateOnScreenIndicators(params, prefs);
203 private void openMenu() {
204 if (mPieRenderer != null) {
205 // If autofocus is not finished, cancel autofocus so that the
206 // subsequent touch can be handled by PreviewGestures
207 if (mController.getCameraState() == PhotoController.FOCUSING) {
208 mController.cancelAutoFocus();
210 mPieRenderer.showInCenter();
214 public void initializeControlByIntent() {
215 mBlocker = mActivity.findViewById(R.id.blocker);
216 mMenuButton = mActivity.findViewById(R.id.menu);
217 mMenuButton.setOnClickListener(new OnClickListener() {
219 public void onClick(View v) {
223 if (mController.isImageCaptureIntent()) {
224 mActivity.hideSwitcher();
225 ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
226 mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
228 mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
229 mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
230 mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
231 mReviewCancelButton.setVisibility(View.VISIBLE);
233 mReviewDoneButton.setOnClickListener(new OnClickListener() {
235 public void onClick(View v) {
236 mController.onCaptureDone();
239 mReviewCancelButton.setOnClickListener(new OnClickListener() {
241 public void onClick(View v) {
242 mController.onCaptureCancelled();
246 mReviewRetakeButton.setOnClickListener(new OnClickListener() {
248 public void onClick(View v) {
249 mController.onCaptureRetake();
255 // called from onResume but only the first time
256 public void initializeFirstTime() {
257 // Initialize shutter button.
258 mShutterButton = mActivity.getShutterButton();
259 mShutterButton.setImageResource(R.drawable.btn_new_shutter);
260 mShutterButton.setOnShutterButtonListener(mController);
261 mShutterButton.setVisibility(View.VISIBLE);
264 // called from onResume every other time
265 public void initializeSecondTime(Camera.Parameters params) {
266 initializeZoom(params);
267 if (mController.isImageCaptureIntent()) {
268 hidePostCaptureAlert();
271 mMenu.reloadPreferences();
275 public void initializeZoom(Camera.Parameters params) {
276 if ((params == null) || !params.isZoomSupported()
277 || (mZoomRenderer == null)) return;
278 mZoomMax = params.getMaxZoom();
279 mZoomRatios = params.getZoomRatios();
280 // Currently we use immediate zoom for fast zooming to get better UX and
281 // there is no plan to take advantage of the smooth zoom.
282 if (mZoomRenderer != null) {
283 mZoomRenderer.setZoomMax(mZoomMax);
284 mZoomRenderer.setZoom(params.getZoom());
285 mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
286 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
290 public void enableGestures(boolean enable) {
291 if (mGestures != null) {
292 mGestures.setEnabled(enable);
296 public void showGpsOnScreenIndicator(boolean hasSignal) { }
298 public void hideGpsOnScreenIndicator() { }
300 public void overrideSettings(final String ... keyvalues) {
301 mMenu.overrideSettings(keyvalues);
304 public void updateOnScreenIndicators(Camera.Parameters params,
305 ComboPreferences prefs) {
306 if (params == null) return;
307 updateSceneOnScreenIndicator(params.getSceneMode());
308 updateExposureOnScreenIndicator(params,
309 CameraSettings.readExposure(prefs));
310 updateFlashOnScreenIndicator(params.getFlashMode());
311 updateHdrOnScreenIndicator(params.getSceneMode());
314 private void updateExposureOnScreenIndicator(Camera.Parameters params, int value) {
315 if (mExposureIndicator == null) {
319 float step = params.getExposureCompensationStep();
320 value = (int) Math.round(value * step);
323 id = R.drawable.ic_indicator_ev_n3;
326 id = R.drawable.ic_indicator_ev_n2;
329 id = R.drawable.ic_indicator_ev_n1;
332 id = R.drawable.ic_indicator_ev_0;
335 id = R.drawable.ic_indicator_ev_p1;
338 id = R.drawable.ic_indicator_ev_p2;
341 id = R.drawable.ic_indicator_ev_p3;
344 mExposureIndicator.setImageResource(id);
347 private void updateFlashOnScreenIndicator(String value) {
348 if (mFlashIndicator == null) {
351 if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
352 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
354 if (Parameters.FLASH_MODE_AUTO.equals(value)) {
355 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
356 } else if (Parameters.FLASH_MODE_ON.equals(value)) {
357 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
359 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
364 private void updateSceneOnScreenIndicator(String value) {
365 if (mSceneIndicator == null) {
368 if ((value == null) || Parameters.SCENE_MODE_AUTO.equals(value)
369 || Parameters.SCENE_MODE_HDR.equals(value)) {
370 mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_off);
372 mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_on);
376 private void updateHdrOnScreenIndicator(String value) {
377 if (mHdrIndicator == null) {
380 if ((value != null) && Parameters.SCENE_MODE_HDR.equals(value)) {
381 mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_on);
383 mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_off);
387 public void setCameraState(int state) {
390 public boolean dispatchTouchEvent(MotionEvent m) {
391 if (mPopup != null) {
392 return mActivity.superDispatchTouchEvent(m);
393 } else if (mGestures != null && mRenderOverlay != null) {
394 return mGestures.dispatchTouch(m);
399 public boolean onBackPressed() {
400 if (mPieRenderer != null && mPieRenderer.showsItems()) {
404 // In image capture mode, back button should:
405 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
407 if (mController.isImageCaptureIntent()) {
408 if (!removeTopLevelPopup()) {
409 // no popup to dismiss, cancel image capture
410 mController.onCaptureCancelled();
413 } else if (!mController.isCameraIdle()) {
414 // ignore backs while we're taking a picture
417 return removeTopLevelPopup();
421 public void onFullScreenChanged(boolean full) {
422 if (mFaceView != null) {
423 mFaceView.setBlockDraw(!full);
425 if (mPopup != null) {
426 dismissPopup(false, full);
428 if (mGestures != null) {
429 mGestures.setEnabled(full);
431 if (mRenderOverlay != null) {
432 // this can not happen in capture mode
433 mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
435 if (mPieRenderer != null) {
436 mPieRenderer.setBlockFocus(!full);
439 if (mBlocker != null) {
440 mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
442 if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
445 public boolean removeTopLevelPopup() {
446 // Remove the top level popup or dialog box and return true if there's any
447 if (mPopup != null) {
454 public void showPopup(AbstractSettingPopup popup) {
456 mBlocker.setVisibility(View.INVISIBLE);
459 mPopup.setVisibility(View.VISIBLE);
460 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
461 LayoutParams.WRAP_CONTENT);
462 lp.gravity = Gravity.CENTER;
463 ((FrameLayout) mRootView).addView(mPopup, lp);
466 public void dismissPopup(boolean topPopupOnly) {
467 dismissPopup(topPopupOnly, true);
470 private void dismissPopup(boolean topOnly, boolean fullScreen) {
473 mBlocker.setVisibility(View.VISIBLE);
475 setShowMenu(fullScreen);
476 if (mPopup != null) {
477 ((FrameLayout) mRootView).removeView(mPopup);
480 mMenu.popupDismissed(topOnly);
483 public void onShowSwitcherPopup() {
484 if (mPieRenderer != null && mPieRenderer.showsItems()) {
489 private void setShowMenu(boolean show) {
490 if (mOnScreenIndicators != null) {
491 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
493 if (mMenuButton != null) {
494 mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
498 public boolean collapseCameraControls() {
499 // Remove all the popups/dialog boxes
501 if (mPopup != null) {
508 protected void showPostCaptureAlert() {
509 mOnScreenIndicators.setVisibility(View.GONE);
510 mMenuButton.setVisibility(View.GONE);
511 Util.fadeIn(mReviewDoneButton);
512 mShutterButton.setVisibility(View.INVISIBLE);
513 Util.fadeIn(mReviewRetakeButton);
516 protected void hidePostCaptureAlert() {
517 mOnScreenIndicators.setVisibility(View.VISIBLE);
518 mMenuButton.setVisibility(View.VISIBLE);
519 Util.fadeOut(mReviewDoneButton);
520 mShutterButton.setVisibility(View.VISIBLE);
521 Util.fadeOut(mReviewRetakeButton);
524 public void setDisplayOrientation(int orientation) {
525 if (mFaceView != null) {
526 mFaceView.setDisplayOrientation(orientation);
530 // shutter button handling
532 public boolean isShutterPressed() {
533 return mShutterButton.isPressed();
539 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
541 public void onZoomValueChanged(int index) {
542 int newZoom = mController.onZoomChanged(index);
543 if (mZoomRenderer != null) {
544 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
549 public void onZoomStart() {
550 if (mPieRenderer != null) {
551 mPieRenderer.setBlockFocus(true);
556 public void onZoomEnd() {
557 if (mPieRenderer != null) {
558 mPieRenderer.setBlockFocus(false);
564 public void onPieOpened(int centerX, int centerY) {
565 mActivity.cancelActivityTouchHandling();
566 mActivity.setSwipingEnabled(false);
567 if (mFaceView != null) {
568 mFaceView.setBlockDraw(true);
573 public void onPieClosed() {
574 mActivity.setSwipingEnabled(true);
575 if (mFaceView != null) {
576 mFaceView.setBlockDraw(false);
583 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
584 Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
589 public void surfaceCreated(SurfaceHolder holder) {
590 Log.v(TAG, "surfaceCreated: " + holder);
591 mSurfaceHolder = holder;
592 mController.onSurfaceCreated(holder);
596 public void surfaceDestroyed(SurfaceHolder holder) {
597 Log.v(TAG, "surfaceDestroyed: " + holder);
598 mSurfaceHolder = null;
599 mController.stopPreview();
602 public Object getSurfaceTexture() {
603 return mSurfaceTexture;
606 public void setSurfaceTexture(Object st) {
607 mSurfaceTexture = st;
610 public SurfaceHolder getSurfaceHolder() {
611 return mSurfaceHolder;
614 public boolean isCountingDown() {
615 return mCountDownView.isCountingDown();
618 public void cancelCountDown() {
619 mCountDownView.cancelCountDown();
622 public void startCountDown(int sec, boolean playSound) {
623 mCountDownView.startCountDown(sec, playSound);
626 public void showPreferencesToast() {
627 if (mNotSelectableToast == null) {
628 String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
629 mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
631 mNotSelectableToast.show();
634 public void onPause() {
635 mCountDownView.cancelCountDown();
636 // Close the camera now because other activities may need to use it.
637 mSurfaceTexture = null;
640 collapseCameraControls();
641 if (mFaceView != null) mFaceView.clear();
644 mRootView.removeOnLayoutChangeListener(mLayoutListener);
649 public void enableShutter(boolean enabled) {
650 if (mShutterButton != null) {
651 mShutterButton.setEnabled(enabled);
655 public void pressShutterButton() {
656 if (mShutterButton.isInTouchMode()) {
657 mShutterButton.requestFocusFromTouch();
659 mShutterButton.requestFocus();
661 mShutterButton.setPressed(true);
664 // forward from preview gestures to controller
666 public void onSingleTapUp(View view, int x, int y) {
667 mController.onSingleTapUp(view, x, y);
670 // focus UI implementation
672 private FocusIndicator getFocusIndicator() {
673 return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
677 public boolean hasFaces() {
678 return (mFaceView != null && mFaceView.faceExists());
681 public void clearFaces() {
682 if (mFaceView != null) mFaceView.clear();
686 public void clearFocus() {
687 FocusIndicator indicator = getFocusIndicator();
688 if (indicator != null) indicator.clear();
692 public void setFocusPosition(int x, int y) {
693 mPieRenderer.setFocus(x, y);
697 public void onFocusStarted() {
698 getFocusIndicator().showStart();
702 public void onFocusSucceeded(boolean timeout) {
703 getFocusIndicator().showSuccess(timeout);
707 public void onFocusFailed(boolean timeout) {
708 getFocusIndicator().showFail(timeout);
712 public void pauseFaceDetection() {
713 if (mFaceView != null) mFaceView.pause();
717 public void resumeFaceDetection() {
718 if (mFaceView != null) mFaceView.resume();
721 public void onStartFaceDetection(int orientation, boolean mirror) {
723 mFaceView.setVisibility(View.VISIBLE);
724 mFaceView.setDisplayOrientation(orientation);
725 mFaceView.setMirror(mirror);
730 public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
731 mFaceView.setFaces(faces);
735 public void onSwipe(int direction) {
736 if (direction == PreviewGestures.DIR_UP) {