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);
149 public View getRootView() {
153 private void initIndicators() {
154 mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
155 mExposureIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_exposure_indicator);
156 mFlashIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_flash_indicator);
157 mSceneIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_scenemode_indicator);
158 mHdrIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_hdr_indicator);
161 public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
162 Camera.Parameters params, OnPreferenceChangedListener listener) {
163 if (mPieRenderer == null) {
164 mPieRenderer = new PieRenderer(mActivity);
165 mPieRenderer.setPieListener(this);
166 mRenderOverlay.addRenderer(mPieRenderer);
169 mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
170 mMenu.setListener(listener);
172 mMenu.initialize(prefGroup);
174 if (mZoomRenderer == null) {
175 mZoomRenderer = new ZoomRenderer(mActivity);
176 mRenderOverlay.addRenderer(mZoomRenderer);
178 if (mGestures == null) {
179 // this will handle gesture disambiguation and dispatching
180 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
184 mGestures.setRenderOverlay(mRenderOverlay);
185 mGestures.addTouchReceiver(mMenuButton);
186 mGestures.addUnclickableArea(mBlocker);
187 // make sure to add touch targets for image capture
188 if (mController.isImageCaptureIntent()) {
189 if (mReviewCancelButton != null) {
190 mGestures.addTouchReceiver(mReviewCancelButton);
192 if (mReviewDoneButton != null) {
193 mGestures.addTouchReceiver(mReviewDoneButton);
196 mRenderOverlay.requestLayout();
198 initializeZoom(params);
199 updateOnScreenIndicators(params, prefs);
202 private void openMenu() {
203 if (mPieRenderer != null) {
204 // If autofocus is not finished, cancel autofocus so that the
205 // subsequent touch can be handled by PreviewGestures
206 if (mController.getCameraState() == PhotoController.FOCUSING) {
207 mController.cancelAutoFocus();
209 mPieRenderer.showInCenter();
213 public void initializeControlByIntent() {
214 mBlocker = mActivity.findViewById(R.id.blocker);
215 mMenuButton = mActivity.findViewById(R.id.menu);
216 mMenuButton.setOnClickListener(new OnClickListener() {
218 public void onClick(View v) {
222 if (mController.isImageCaptureIntent()) {
223 mActivity.hideSwitcher();
224 ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
225 mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
227 mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
228 mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
229 mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
230 mReviewCancelButton.setVisibility(View.VISIBLE);
232 mReviewDoneButton.setOnClickListener(new OnClickListener() {
234 public void onClick(View v) {
235 mController.onCaptureDone();
238 mReviewCancelButton.setOnClickListener(new OnClickListener() {
240 public void onClick(View v) {
241 mController.onCaptureCancelled();
245 mReviewRetakeButton.setOnClickListener(new OnClickListener() {
247 public void onClick(View v) {
248 mController.onCaptureRetake();
254 // called from onResume but only the first time
255 public void initializeFirstTime() {
256 // Initialize shutter button.
257 mShutterButton = mActivity.getShutterButton();
258 mShutterButton.setImageResource(R.drawable.btn_new_shutter);
259 mShutterButton.setOnShutterButtonListener(mController);
260 mShutterButton.setVisibility(View.VISIBLE);
261 mRootView.addOnLayoutChangeListener(mLayoutListener);
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();
273 mRootView.addOnLayoutChangeListener(mLayoutListener);
276 public void initializeZoom(Camera.Parameters params) {
277 if ((params == null) || !params.isZoomSupported()
278 || (mZoomRenderer == null)) return;
279 mZoomMax = params.getMaxZoom();
280 mZoomRatios = params.getZoomRatios();
281 // Currently we use immediate zoom for fast zooming to get better UX and
282 // there is no plan to take advantage of the smooth zoom.
283 if (mZoomRenderer != null) {
284 mZoomRenderer.setZoomMax(mZoomMax);
285 mZoomRenderer.setZoom(params.getZoom());
286 mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
287 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
291 public void enableGestures(boolean enable) {
292 if (mGestures != null) {
293 mGestures.setEnabled(enable);
297 public void showGpsOnScreenIndicator(boolean hasSignal) { }
299 public void hideGpsOnScreenIndicator() { }
301 public void overrideSettings(final String ... keyvalues) {
302 mMenu.overrideSettings(keyvalues);
305 public void updateOnScreenIndicators(Camera.Parameters params,
306 ComboPreferences prefs) {
307 if (params == null) return;
308 updateSceneOnScreenIndicator(params.getSceneMode());
309 updateExposureOnScreenIndicator(params,
310 CameraSettings.readExposure(prefs));
311 updateFlashOnScreenIndicator(params.getFlashMode());
312 updateHdrOnScreenIndicator(params.getSceneMode());
315 private void updateExposureOnScreenIndicator(Camera.Parameters params, int value) {
316 if (mExposureIndicator == null) {
320 float step = params.getExposureCompensationStep();
321 value = (int) Math.round(value * step);
324 id = R.drawable.ic_indicator_ev_n3;
327 id = R.drawable.ic_indicator_ev_n2;
330 id = R.drawable.ic_indicator_ev_n1;
333 id = R.drawable.ic_indicator_ev_0;
336 id = R.drawable.ic_indicator_ev_p1;
339 id = R.drawable.ic_indicator_ev_p2;
342 id = R.drawable.ic_indicator_ev_p3;
345 mExposureIndicator.setImageResource(id);
348 private void updateFlashOnScreenIndicator(String value) {
349 if (mFlashIndicator == null) {
352 if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
353 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
355 if (Parameters.FLASH_MODE_AUTO.equals(value)) {
356 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
357 } else if (Parameters.FLASH_MODE_ON.equals(value)) {
358 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
360 mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
365 private void updateSceneOnScreenIndicator(String value) {
366 if (mSceneIndicator == null) {
369 if ((value == null) || Parameters.SCENE_MODE_AUTO.equals(value)
370 || Parameters.SCENE_MODE_HDR.equals(value)) {
371 mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_off);
373 mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_on);
377 private void updateHdrOnScreenIndicator(String value) {
378 if (mHdrIndicator == null) {
381 if ((value != null) && Parameters.SCENE_MODE_HDR.equals(value)) {
382 mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_on);
384 mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_off);
388 public void setCameraState(int state) {
391 public boolean dispatchTouchEvent(MotionEvent m) {
392 if (mPopup != null) {
393 return mActivity.superDispatchTouchEvent(m);
394 } else if (mGestures != null && mRenderOverlay != null) {
395 return mGestures.dispatchTouch(m);
400 public boolean onBackPressed() {
401 if (mPieRenderer != null && mPieRenderer.showsItems()) {
405 // In image capture mode, back button should:
406 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
408 if (mController.isImageCaptureIntent()) {
409 if (!removeTopLevelPopup()) {
410 // no popup to dismiss, cancel image capture
411 mController.onCaptureCancelled();
414 } else if (!mController.isCameraIdle()) {
415 // ignore backs while we're taking a picture
418 return removeTopLevelPopup();
422 public void onFullScreenChanged(boolean full) {
423 if (mFaceView != null) {
424 mFaceView.setBlockDraw(!full);
426 if (mPopup != null) {
427 dismissPopup(false, full);
429 if (mGestures != null) {
430 mGestures.setEnabled(full);
432 if (mRenderOverlay != null) {
433 // this can not happen in capture mode
434 mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
436 if (mPieRenderer != null) {
437 mPieRenderer.setBlockFocus(!full);
440 if (mBlocker != null) {
441 mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
443 if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
446 public boolean removeTopLevelPopup() {
447 // Remove the top level popup or dialog box and return true if there's any
448 if (mPopup != null) {
455 public void showPopup(AbstractSettingPopup popup) {
457 mBlocker.setVisibility(View.INVISIBLE);
460 mPopup.setVisibility(View.VISIBLE);
461 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
462 LayoutParams.WRAP_CONTENT);
463 lp.gravity = Gravity.CENTER;
464 ((FrameLayout) mRootView).addView(mPopup, lp);
467 public void dismissPopup(boolean topPopupOnly) {
468 dismissPopup(topPopupOnly, true);
471 private void dismissPopup(boolean topOnly, boolean fullScreen) {
474 mBlocker.setVisibility(View.VISIBLE);
476 setShowMenu(fullScreen);
477 if (mPopup != null) {
478 ((FrameLayout) mRootView).removeView(mPopup);
481 mMenu.popupDismissed(topOnly);
484 public void onShowSwitcherPopup() {
485 if (mPieRenderer != null && mPieRenderer.showsItems()) {
490 private void setShowMenu(boolean show) {
491 if (mOnScreenIndicators != null) {
492 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
494 if (mMenuButton != null) {
495 mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
499 public boolean collapseCameraControls() {
500 // Remove all the popups/dialog boxes
502 if (mPopup != null) {
509 protected void showPostCaptureAlert() {
510 mOnScreenIndicators.setVisibility(View.GONE);
511 mMenuButton.setVisibility(View.GONE);
512 Util.fadeIn(mReviewDoneButton);
513 mShutterButton.setVisibility(View.INVISIBLE);
514 Util.fadeIn(mReviewRetakeButton);
517 protected void hidePostCaptureAlert() {
518 mOnScreenIndicators.setVisibility(View.VISIBLE);
519 mMenuButton.setVisibility(View.VISIBLE);
520 Util.fadeOut(mReviewDoneButton);
521 mShutterButton.setVisibility(View.VISIBLE);
522 Util.fadeOut(mReviewRetakeButton);
525 public void setDisplayOrientation(int orientation) {
526 if (mFaceView != null) {
527 mFaceView.setDisplayOrientation(orientation);
531 // shutter button handling
533 public boolean isShutterPressed() {
534 return mShutterButton.isPressed();
540 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
542 public void onZoomValueChanged(int index) {
543 int newZoom = mController.onZoomChanged(index);
544 if (mZoomRenderer != null) {
545 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
550 public void onZoomStart() {
551 if (mPieRenderer != null) {
552 mPieRenderer.setBlockFocus(true);
557 public void onZoomEnd() {
558 if (mPieRenderer != null) {
559 mPieRenderer.setBlockFocus(false);
565 public void onPieOpened(int centerX, int centerY) {
566 mActivity.cancelActivityTouchHandling();
567 mActivity.setSwipingEnabled(false);
568 if (mFaceView != null) {
569 mFaceView.setBlockDraw(true);
574 public void onPieClosed() {
575 mActivity.setSwipingEnabled(true);
576 if (mFaceView != null) {
577 mFaceView.setBlockDraw(false);
584 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
585 Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
590 public void surfaceCreated(SurfaceHolder holder) {
591 Log.v(TAG, "surfaceCreated: " + holder);
592 mSurfaceHolder = holder;
593 mController.onSurfaceCreated(holder);
597 public void surfaceDestroyed(SurfaceHolder holder) {
598 Log.v(TAG, "surfaceDestroyed: " + holder);
599 mSurfaceHolder = null;
600 mController.stopPreview();
603 public Object getSurfaceTexture() {
604 return mSurfaceTexture;
607 public void setSurfaceTexture(Object st) {
608 mSurfaceTexture = st;
611 public SurfaceHolder getSurfaceHolder() {
612 return mSurfaceHolder;
615 public boolean isCountingDown() {
616 return mCountDownView.isCountingDown();
619 public void cancelCountDown() {
620 mCountDownView.cancelCountDown();
623 public void startCountDown(int sec, boolean playSound) {
624 mCountDownView.startCountDown(sec, playSound);
627 public void showPreferencesToast() {
628 if (mNotSelectableToast == null) {
629 String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
630 mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
632 mNotSelectableToast.show();
635 public void onPause() {
636 mCountDownView.cancelCountDown();
637 // Close the camera now because other activities may need to use it.
638 mSurfaceTexture = null;
641 collapseCameraControls();
642 if (mFaceView != null) mFaceView.clear();
645 mRootView.removeOnLayoutChangeListener(mLayoutListener);
650 public void enableShutter(boolean enabled) {
651 if (mShutterButton != null) {
652 mShutterButton.setEnabled(enabled);
656 public void pressShutterButton() {
657 if (mShutterButton.isInTouchMode()) {
658 mShutterButton.requestFocusFromTouch();
660 mShutterButton.requestFocus();
662 mShutterButton.setPressed(true);
665 // forward from preview gestures to controller
667 public void onSingleTapUp(View view, int x, int y) {
668 mController.onSingleTapUp(view, x, y);
671 // focus UI implementation
673 private FocusIndicator getFocusIndicator() {
674 return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
678 public boolean hasFaces() {
679 return (mFaceView != null && mFaceView.faceExists());
682 public void clearFaces() {
683 if (mFaceView != null) mFaceView.clear();
687 public void clearFocus() {
688 FocusIndicator indicator = getFocusIndicator();
689 if (indicator != null) indicator.clear();
693 public void setFocusPosition(int x, int y) {
694 mPieRenderer.setFocus(x, y);
698 public void onFocusStarted() {
699 getFocusIndicator().showStart();
703 public void onFocusSucceeded(boolean timeout) {
704 getFocusIndicator().showSuccess(timeout);
708 public void onFocusFailed(boolean timeout) {
709 getFocusIndicator().showFail(timeout);
713 public void pauseFaceDetection() {
714 if (mFaceView != null) mFaceView.pause();
718 public void resumeFaceDetection() {
719 if (mFaceView != null) mFaceView.resume();
722 public void onStartFaceDetection(int orientation, boolean mirror) {
724 mFaceView.setVisibility(View.VISIBLE);
725 mFaceView.setDisplayOrientation(orientation);
726 mFaceView.setMirror(mirror);
731 public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
732 mFaceView.setFaces(faces);
736 public void onSwipe(int direction) {
737 if (direction == PreviewGestures.DIR_UP) {