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;
95 private View mPreviewThumb;
97 private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
99 public void onLayoutChange(View v, int left, int top, int right,
100 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
101 int width = right - left;
102 int height = bottom - top;
103 // Full-screen screennail
106 if (Util.getDisplayRotation(mActivity) % 180 != 0) {
110 if (mPreviewWidth != w || mPreviewHeight != h) {
113 mController.onScreenSizeChanged(width, height, w, h);
118 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
119 mActivity = activity;
120 mController = controller;
123 mActivity.getLayoutInflater().inflate(R.layout.photo_module,
124 (ViewGroup) mRootView, true);
125 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
128 mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
129 mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
131 if (ApiHelper.HAS_FACE_DETECTION) {
132 ViewStub faceViewStub = (ViewStub) mRootView
133 .findViewById(R.id.face_view_stub);
134 if (faceViewStub != null) {
135 faceViewStub.inflate();
136 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
142 public View getRootView() {
146 private void initIndicators() {
147 mOnScreenIndicators = new OnScreenIndicators(mActivity,
148 mActivity.findViewById(R.id.on_screen_indicators));
151 public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
152 Camera.Parameters params, OnPreferenceChangedListener listener) {
153 if (mPieRenderer == null) {
154 mPieRenderer = new PieRenderer(mActivity);
155 mPieRenderer.setPieListener(this);
156 mRenderOverlay.addRenderer(mPieRenderer);
159 mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
160 mMenu.setListener(listener);
162 mMenu.initialize(prefGroup);
164 if (mZoomRenderer == null) {
165 mZoomRenderer = new ZoomRenderer(mActivity);
166 mRenderOverlay.addRenderer(mZoomRenderer);
168 if (mGestures == null) {
169 // this will handle gesture disambiguation and dispatching
170 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
174 mGestures.setRenderOverlay(mRenderOverlay);
175 mGestures.addTouchReceiver(mMenuButton);
176 mGestures.addUnclickableArea(mBlocker);
177 enablePreviewThumb(false);
178 // make sure to add touch targets for image capture
179 if (mController.isImageCaptureIntent()) {
180 if (mReviewCancelButton != null) {
181 mGestures.addTouchReceiver(mReviewCancelButton);
183 if (mReviewDoneButton != null) {
184 mGestures.addTouchReceiver(mReviewDoneButton);
187 mRenderOverlay.requestLayout();
189 initializeZoom(params);
190 updateOnScreenIndicators(params, prefGroup, prefs);
193 private void openMenu() {
194 if (mPieRenderer != null) {
195 // If autofocus is not finished, cancel autofocus so that the
196 // subsequent touch can be handled by PreviewGestures
197 if (mController.getCameraState() == PhotoController.FOCUSING) {
198 mController.cancelAutoFocus();
200 mPieRenderer.showInCenter();
204 public void initializeControlByIntent() {
205 mBlocker = mActivity.findViewById(R.id.blocker);
206 mPreviewThumb = mActivity.findViewById(R.id.preview_thumb);
207 mPreviewThumb.setOnClickListener(new OnClickListener() {
209 public void onClick(View v) {
210 mActivity.gotoGallery();
213 mMenuButton = mActivity.findViewById(R.id.menu);
214 mMenuButton.setOnClickListener(new OnClickListener() {
216 public void onClick(View v) {
220 if (mController.isImageCaptureIntent()) {
221 mActivity.hideSwitcher();
222 ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
223 mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
225 mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
226 mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
227 mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
228 mReviewCancelButton.setVisibility(View.VISIBLE);
230 mReviewDoneButton.setOnClickListener(new OnClickListener() {
232 public void onClick(View v) {
233 mController.onCaptureDone();
236 mReviewCancelButton.setOnClickListener(new OnClickListener() {
238 public void onClick(View v) {
239 mController.onCaptureCancelled();
243 mReviewRetakeButton.setOnClickListener(new OnClickListener() {
245 public void onClick(View v) {
246 mController.onCaptureRetake();
252 // called from onResume but only the first time
253 public void initializeFirstTime() {
254 // Initialize shutter button.
255 mShutterButton = mActivity.getShutterButton();
256 mShutterButton.setImageResource(R.drawable.btn_new_shutter);
257 mShutterButton.setOnShutterButtonListener(mController);
258 mShutterButton.setVisibility(View.VISIBLE);
259 mRootView.addOnLayoutChangeListener(mLayoutListener);
262 // called from onResume every other time
263 public void initializeSecondTime(Camera.Parameters params) {
264 initializeZoom(params);
265 if (mController.isImageCaptureIntent()) {
266 hidePostCaptureAlert();
269 mMenu.reloadPreferences();
271 mRootView.addOnLayoutChangeListener(mLayoutListener);
274 public void initializeZoom(Camera.Parameters params) {
275 if ((params == null) || !params.isZoomSupported()
276 || (mZoomRenderer == null)) return;
277 mZoomMax = params.getMaxZoom();
278 mZoomRatios = params.getZoomRatios();
279 // Currently we use immediate zoom for fast zooming to get better UX and
280 // there is no plan to take advantage of the smooth zoom.
281 if (mZoomRenderer != null) {
282 mZoomRenderer.setZoomMax(mZoomMax);
283 mZoomRenderer.setZoom(params.getZoom());
284 mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
285 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
289 public void enableGestures(boolean enable) {
290 if (mGestures != null) {
291 mGestures.setEnabled(enable);
296 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 PreferenceGroup group, ComboPreferences prefs) {
307 if (params == null) return;
308 mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode());
309 mOnScreenIndicators.updateExposureOnScreenIndicator(params,
310 CameraSettings.readExposure(prefs));
311 mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode());
313 ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE);
315 wbIndex = pref.getCurrentIndex();
317 mOnScreenIndicators.updateWBIndicator(wbIndex);
318 boolean location = RecordLocationPreference.get(
319 prefs, mActivity.getContentResolver());
320 mOnScreenIndicators.updateLocationIndicator(location);
323 public void setCameraState(int state) {
326 public boolean dispatchTouchEvent(MotionEvent m) {
327 if (mGestures != null && mRenderOverlay != null) {
328 return mGestures.dispatchTouch(m);
333 public boolean onBackPressed() {
334 if (mPieRenderer != null && mPieRenderer.showsItems()) {
338 // In image capture mode, back button should:
339 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
341 if (mController.isImageCaptureIntent()) {
342 if (!removeTopLevelPopup()) {
343 // no popup to dismiss, cancel image capture
344 mController.onCaptureCancelled();
347 } else if (!mController.isCameraIdle()) {
348 // ignore backs while we're taking a picture
351 return removeTopLevelPopup();
355 public void onFullScreenChanged(boolean full) {
356 if (mFaceView != null) {
357 mFaceView.setBlockDraw(!full);
359 if (mPopup != null) {
362 if (mGestures != null) {
363 mGestures.setEnabled(full);
365 if (mRenderOverlay != null) {
366 // this can not happen in capture mode
367 mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
369 if (mPieRenderer != null) {
370 mPieRenderer.setBlockFocus(!full);
373 if (mBlocker != null) {
374 mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
376 if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
379 public void enablePreviewThumb(boolean enabled) {
381 mGestures.addTouchReceiver(mPreviewThumb);
382 mPreviewThumb.setVisibility(View.VISIBLE);
384 mGestures.removeTouchReceiver(mPreviewThumb);
385 mPreviewThumb.setVisibility(View.GONE);
389 public boolean removeTopLevelPopup() {
390 // Remove the top level popup or dialog box and return true if there's any
391 if (mPopup != null) {
398 public void showPopup(AbstractSettingPopup popup) {
400 mBlocker.setVisibility(View.INVISIBLE);
403 mPopup.setVisibility(View.VISIBLE);
404 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
405 LayoutParams.WRAP_CONTENT);
406 lp.gravity = Gravity.CENTER;
407 ((FrameLayout) mRootView).addView(mPopup, lp);
408 mGestures.addTouchReceiver(mPopup);
411 public void dismissPopup() {
415 private void dismissPopup(boolean fullScreen) {
418 mBlocker.setVisibility(View.VISIBLE);
420 setShowMenu(fullScreen);
421 if (mPopup != null) {
422 mGestures.removeTouchReceiver(mPopup);
423 ((FrameLayout) mRootView).removeView(mPopup);
426 mMenu.popupDismissed();
429 public void onShowSwitcherPopup() {
430 if (mPieRenderer != null && mPieRenderer.showsItems()) {
435 private void setShowMenu(boolean show) {
436 if (mOnScreenIndicators != null) {
437 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
439 if (mMenuButton != null) {
440 mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
444 public boolean collapseCameraControls() {
445 // Remove all the popups/dialog boxes
447 if (mPopup != null) {
454 protected void showPostCaptureAlert() {
455 mOnScreenIndicators.setVisibility(View.GONE);
456 mMenuButton.setVisibility(View.GONE);
457 Util.fadeIn(mReviewDoneButton);
458 mShutterButton.setVisibility(View.INVISIBLE);
459 Util.fadeIn(mReviewRetakeButton);
462 protected void hidePostCaptureAlert() {
463 mOnScreenIndicators.setVisibility(View.VISIBLE);
464 mMenuButton.setVisibility(View.VISIBLE);
465 Util.fadeOut(mReviewDoneButton);
466 mShutterButton.setVisibility(View.VISIBLE);
467 Util.fadeOut(mReviewRetakeButton);
470 public void setDisplayOrientation(int orientation) {
471 if (mFaceView != null) {
472 mFaceView.setDisplayOrientation(orientation);
476 // shutter button handling
478 public boolean isShutterPressed() {
479 return mShutterButton.isPressed();
485 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
487 public void onZoomValueChanged(int index) {
488 int newZoom = mController.onZoomChanged(index);
489 if (mZoomRenderer != null) {
490 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
495 public void onZoomStart() {
496 if (mPieRenderer != null) {
497 mPieRenderer.setBlockFocus(true);
502 public void onZoomEnd() {
503 if (mPieRenderer != null) {
504 mPieRenderer.setBlockFocus(false);
510 public void onPieOpened(int centerX, int centerY) {
512 mActivity.cancelActivityTouchHandling();
513 mActivity.setSwipingEnabled(false);
514 if (mFaceView != null) {
515 mFaceView.setBlockDraw(true);
520 public void onPieClosed() {
521 mActivity.setSwipingEnabled(true);
522 if (mFaceView != null) {
523 mFaceView.setBlockDraw(false);
530 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
531 Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
536 public void surfaceCreated(SurfaceHolder holder) {
537 Log.v(TAG, "surfaceCreated: " + holder);
538 mSurfaceHolder = holder;
539 mController.onSurfaceCreated(holder);
543 public void surfaceDestroyed(SurfaceHolder holder) {
544 Log.v(TAG, "surfaceDestroyed: " + holder);
545 mSurfaceHolder = null;
546 mController.stopPreview();
549 public Object getSurfaceTexture() {
550 return mSurfaceTexture;
553 public void setSurfaceTexture(Object st) {
554 mSurfaceTexture = st;
557 public SurfaceHolder getSurfaceHolder() {
558 return mSurfaceHolder;
561 public boolean isCountingDown() {
562 return mCountDownView.isCountingDown();
565 public void cancelCountDown() {
566 mCountDownView.cancelCountDown();
569 public void startCountDown(int sec, boolean playSound) {
570 mCountDownView.startCountDown(sec, playSound);
573 public void showPreferencesToast() {
574 if (mNotSelectableToast == null) {
575 String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
576 mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
578 mNotSelectableToast.show();
581 public void onPause() {
582 mCountDownView.cancelCountDown();
583 // Close the camera now because other activities may need to use it.
584 mSurfaceTexture = null;
587 collapseCameraControls();
588 if (mFaceView != null) mFaceView.clear();
591 mRootView.removeOnLayoutChangeListener(mLayoutListener);
596 public void enableShutter(boolean enabled) {
597 if (mShutterButton != null) {
598 mShutterButton.setEnabled(enabled);
602 public void pressShutterButton() {
603 if (mShutterButton.isInTouchMode()) {
604 mShutterButton.requestFocusFromTouch();
606 mShutterButton.requestFocus();
608 mShutterButton.setPressed(true);
611 // forward from preview gestures to controller
613 public void onSingleTapUp(View view, int x, int y) {
614 mController.onSingleTapUp(view, x, y);
617 // focus UI implementation
619 private FocusIndicator getFocusIndicator() {
620 return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
624 public boolean hasFaces() {
625 return (mFaceView != null && mFaceView.faceExists());
628 public void clearFaces() {
629 if (mFaceView != null) mFaceView.clear();
633 public void clearFocus() {
634 FocusIndicator indicator = getFocusIndicator();
635 if (indicator != null) indicator.clear();
639 public void setFocusPosition(int x, int y) {
640 mPieRenderer.setFocus(x, y);
644 public void onFocusStarted() {
645 getFocusIndicator().showStart();
649 public void onFocusSucceeded(boolean timeout) {
650 getFocusIndicator().showSuccess(timeout);
654 public void onFocusFailed(boolean timeout) {
655 getFocusIndicator().showFail(timeout);
659 public void pauseFaceDetection() {
660 if (mFaceView != null) mFaceView.pause();
664 public void resumeFaceDetection() {
665 if (mFaceView != null) mFaceView.resume();
668 public void onStartFaceDetection(int orientation, boolean mirror) {
670 mFaceView.setVisibility(View.VISIBLE);
671 mFaceView.setDisplayOrientation(orientation);
672 mFaceView.setMirror(mirror);
677 public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
678 mFaceView.setFaces(faces);
682 public void onSwipe(int direction) {
683 if (direction == PreviewGestures.DIR_UP) {