/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera; import android.graphics.Matrix; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.Camera.Face; import android.hardware.Camera.FaceDetectionListener; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.TextureView; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.view.ViewStub; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import android.widget.ImageView; import android.widget.Toast; import com.android.camera.CameraPreference.OnPreferenceChangedListener; import com.android.camera.FocusOverlayManager.FocusUI; import com.android.camera.ui.AbstractSettingPopup; import com.android.camera.ui.CameraControls; import com.android.camera.ui.CameraSwitcher.CameraSwitchListener; import com.android.camera.ui.CountDownView; import com.android.camera.ui.CountDownView.OnCountDownFinishedListener; import com.android.camera.ui.CameraSwitcher; import com.android.camera.ui.FaceView; import com.android.camera.ui.FocusIndicator; import com.android.camera.ui.NewCameraRootView; import com.android.camera.ui.PieRenderer; import com.android.camera.ui.PieRenderer.PieListener; import com.android.camera.ui.RenderOverlay; import com.android.camera.ui.ZoomRenderer; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import java.io.IOException; import java.util.List; public class NewPhotoUI implements PieListener, NewPreviewGestures.SingleTapListener, FocusUI, TextureView.SurfaceTextureListener, LocationManager.Listener, NewCameraRootView.MyDisplayListener, FaceDetectionListener { private static final String TAG = "CAM_UI"; private static final int UPDATE_TRANSFORM_MATRIX = 1; private NewCameraActivity mActivity; private PhotoController mController; private NewPreviewGestures mGestures; private View mRootView; private Object mSurfaceTexture; private AbstractSettingPopup mPopup; private ShutterButton mShutterButton; private CountDownView mCountDownView; private FaceView mFaceView; private RenderOverlay mRenderOverlay; private View mReviewCancelButton; private View mReviewDoneButton; private View mReviewRetakeButton; private View mMenuButton; private View mBlocker; private NewPhotoMenu mMenu; private CameraSwitcher mSwitcher; private CameraControls mCameraControls; // Small indicators which show the camera settings in the viewfinder. private OnScreenIndicators mOnScreenIndicators; private PieRenderer mPieRenderer; private ZoomRenderer mZoomRenderer; private Toast mNotSelectableToast; private int mZoomMax; private List mZoomRatios; private int mPreviewWidth = 0; private int mPreviewHeight = 0; private float mSurfaceTextureUncroppedWidth; private float mSurfaceTextureUncroppedHeight; private View mPreviewThumb; private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener; private TextureView mTextureView; private Matrix mMatrix = null; private float mAspectRatio = 4f / 3f; private final Object mLock = new Object(); private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_TRANSFORM_MATRIX: setTransformMatrix(mPreviewWidth, mPreviewHeight); break; default: break; } } }; public interface SurfaceTextureSizeChangedListener { public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight); } private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { int width = right - left; int height = bottom - top; // Full-screen screennail int w = width; int h = height; if (Util.getDisplayRotation(mActivity) % 180 != 0) { w = height; h = width; } if (mPreviewWidth != width || mPreviewHeight != height) { mPreviewWidth = width; mPreviewHeight = height; onScreenSizeChanged(width, height, w, h); mController.onScreenSizeChanged(width, height, w, h); } } }; public NewPhotoUI(NewCameraActivity activity, PhotoController controller, View parent) { mActivity = activity; mController = controller; mRootView = parent; mActivity.getLayoutInflater().inflate(R.layout.new_photo_module, (ViewGroup) mRootView, true); mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); // display the view mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); mTextureView.setSurfaceTextureListener(this); mTextureView.addOnLayoutChangeListener(mLayoutListener); initIndicators(); mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button); mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher); mSwitcher.setCurrentIndex(0); mSwitcher.setSwitchListener((CameraSwitchListener) mActivity); mMenuButton = mRootView.findViewById(R.id.menu); if (ApiHelper.HAS_FACE_DETECTION) { ViewStub faceViewStub = (ViewStub) mRootView .findViewById(R.id.face_view_stub); if (faceViewStub != null) { faceViewStub.inflate(); mFaceView = (FaceView) mRootView.findViewById(R.id.face_view); setSurfaceTextureSizeChangedListener( (SurfaceTextureSizeChangedListener) mFaceView); } } mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls); ((NewCameraRootView) mRootView).setDisplayChangeListener(this); } public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) { setTransformMatrix(width, height); } public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) { mSurfaceTextureSizeListener = listener; } public void setPreviewSize(Size size) { int width = size.width; int height = size.height; if (width == 0 || height == 0) { Log.w(TAG, "Preview size should not be 0."); return; } if (width > height) { mAspectRatio = (float) width / height; } else { mAspectRatio = (float) height / width; } mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX); } private void setTransformMatrix(int width, int height) { mMatrix = mTextureView.getTransform(mMatrix); int orientation = Util.getDisplayRotation(mActivity); float scaleX = 1f, scaleY = 1f; float scaledTextureWidth, scaledTextureHeight; if (width > height) { scaledTextureWidth = Math.max(width, (int) (height * mAspectRatio)); scaledTextureHeight = Math.max(height, (int)(width / mAspectRatio)); } else { scaledTextureWidth = Math.max(width, (int) (height / mAspectRatio)); scaledTextureHeight = Math.max(height, (int) (width * mAspectRatio)); } if (mSurfaceTextureUncroppedWidth != scaledTextureWidth || mSurfaceTextureUncroppedHeight != scaledTextureHeight) { mSurfaceTextureUncroppedWidth = scaledTextureWidth; mSurfaceTextureUncroppedHeight = scaledTextureHeight; if (mSurfaceTextureSizeListener != null) { mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged( (int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight); } } scaleX = scaledTextureWidth / width; scaleY = scaledTextureHeight / height; mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2); mTextureView.setTransform(mMatrix); } public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { synchronized (mLock) { mSurfaceTexture = surface; mLock.notifyAll(); } } public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Ignored, Camera does all the work for us } public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { mSurfaceTexture = null; mController.stopPreview(); Log.w(TAG, "surfaceTexture is destroyed"); return true; } public void onSurfaceTextureUpdated(SurfaceTexture surface) { // Invoked every time there's a new Camera preview frame } public View getRootView() { return mRootView; } private void initIndicators() { mOnScreenIndicators = new OnScreenIndicators(mActivity, mRootView.findViewById(R.id.on_screen_indicators)); } public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs, Camera.Parameters params, OnPreferenceChangedListener listener) { if (mPieRenderer == null) { mPieRenderer = new PieRenderer(mActivity); mPieRenderer.setPieListener(this); mRenderOverlay.addRenderer(mPieRenderer); } if (mMenu == null) { mMenu = new NewPhotoMenu(mActivity, this, mPieRenderer); mMenu.setListener(listener); } mMenu.initialize(prefGroup); if (mZoomRenderer == null) { mZoomRenderer = new ZoomRenderer(mActivity); mRenderOverlay.addRenderer(mZoomRenderer); } if (mGestures == null) { // this will handle gesture disambiguation and dispatching mGestures = new NewPreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); mRenderOverlay.setGestures(mGestures); } mGestures.setZoomEnabled(params.isZoomSupported()); mGestures.setRenderOverlay(mRenderOverlay); mRenderOverlay.requestLayout(); initializeZoom(params); updateOnScreenIndicators(params, prefGroup, prefs); } private void openMenu() { if (mPieRenderer != null) { // If autofocus is not finished, cancel autofocus so that the // subsequent touch can be handled by PreviewGestures if (mController.getCameraState() == PhotoController.FOCUSING) { mController.cancelAutoFocus(); } mPieRenderer.showInCenter(); } } public void initializeControlByIntent() { mBlocker = mRootView.findViewById(R.id.blocker); mPreviewThumb = mActivity.findViewById(R.id.preview_thumb); mPreviewThumb.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO: go to filmstrip // mActivity.gotoGallery(); } }); mMenuButton = mRootView.findViewById(R.id.menu); mMenuButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { openMenu(); } }); if (mController.isImageCaptureIntent()) { hideSwitcher(); ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls); mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); mReviewDoneButton = mRootView.findViewById(R.id.btn_done); mReviewCancelButton = mRootView.findViewById(R.id.btn_cancel); mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake); mReviewCancelButton.setVisibility(View.VISIBLE); mReviewDoneButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mController.onCaptureDone(); } }); mReviewCancelButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mController.onCaptureCancelled(); } }); mReviewRetakeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mController.onCaptureRetake(); } }); } } public void hideUI() { mCameraControls.setVisibility(View.INVISIBLE); mSwitcher.closePopup(); } public void showUI() { mCameraControls.setVisibility(View.VISIBLE); } public void hideSwitcher() { mSwitcher.closePopup(); mSwitcher.setVisibility(View.INVISIBLE); } public void showSwitcher() { mSwitcher.setVisibility(View.VISIBLE); } // called from onResume but only the first time public void initializeFirstTime() { // Initialize shutter button. mShutterButton.setImageResource(R.drawable.btn_new_shutter); mShutterButton.setOnShutterButtonListener(mController); mShutterButton.setVisibility(View.VISIBLE); } // called from onResume every other time public void initializeSecondTime(Camera.Parameters params) { initializeZoom(params); if (mController.isImageCaptureIntent()) { hidePostCaptureAlert(); } if (mMenu != null) { mMenu.reloadPreferences(); } } public void initializeZoom(Camera.Parameters params) { if ((params == null) || !params.isZoomSupported() || (mZoomRenderer == null)) return; mZoomMax = params.getMaxZoom(); mZoomRatios = params.getZoomRatios(); // Currently we use immediate zoom for fast zooming to get better UX and // there is no plan to take advantage of the smooth zoom. if (mZoomRenderer != null) { mZoomRenderer.setZoomMax(mZoomMax); mZoomRenderer.setZoom(params.getZoom()); mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom())); mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); } } public void showGpsOnScreenIndicator(boolean hasSignal) { } public void hideGpsOnScreenIndicator() { } public void overrideSettings(final String ... keyvalues) { mMenu.overrideSettings(keyvalues); } public void updateOnScreenIndicators(Camera.Parameters params, PreferenceGroup group, ComboPreferences prefs) { if (params == null) return; mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode()); mOnScreenIndicators.updateExposureOnScreenIndicator(params, CameraSettings.readExposure(prefs)); mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode()); int wbIndex = 2; ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE); if (pref != null) { wbIndex = pref.getCurrentIndex(); } mOnScreenIndicators.updateWBIndicator(wbIndex); boolean location = RecordLocationPreference.get( prefs, mActivity.getContentResolver()); mOnScreenIndicators.updateLocationIndicator(location); } public void setCameraState(int state) { } public void enableGestures(boolean enable) { if (mGestures != null) { mGestures.setEnabled(enable); } } // forward from preview gestures to controller @Override public void onSingleTapUp(View view, int x, int y) { mController.onSingleTapUp(view, x, y); } public boolean onBackPressed() { if (mPieRenderer != null && mPieRenderer.showsItems()) { mPieRenderer.hide(); return true; } // In image capture mode, back button should: // 1) if there is any popup, dismiss them, 2) otherwise, get out of // image capture if (mController.isImageCaptureIntent()) { if (!removeTopLevelPopup()) { // no popup to dismiss, cancel image capture mController.onCaptureCancelled(); } return true; } else if (!mController.isCameraIdle()) { // ignore backs while we're taking a picture return true; } else { return removeTopLevelPopup(); } } public void onSwitchMode(boolean toCamera) { if (toCamera) { showUI(); } else { hideUI(); } if (mFaceView != null) { mFaceView.setBlockDraw(!toCamera); } if (mPopup != null) { dismissPopup(toCamera); } if (mGestures != null) { mGestures.setEnabled(toCamera); } if (mRenderOverlay != null) { // this can not happen in capture mode mRenderOverlay.setVisibility(toCamera ? View.VISIBLE : View.GONE); } if (mPieRenderer != null) { mPieRenderer.setBlockFocus(!toCamera); } setShowMenu(toCamera); if (!toCamera && mCountDownView != null) mCountDownView.cancelCountDown(); } public void enablePreviewThumb(boolean enabled) { if (enabled) { mPreviewThumb.setVisibility(View.VISIBLE); } else { mPreviewThumb.setVisibility(View.GONE); } } public boolean removeTopLevelPopup() { // Remove the top level popup or dialog box and return true if there's any if (mPopup != null) { dismissPopup(); return true; } return false; } public void showPopup(AbstractSettingPopup popup) { hideUI(); mBlocker.setVisibility(View.INVISIBLE); setShowMenu(false); mPopup = popup; mPopup.setVisibility(View.VISIBLE); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER; ((FrameLayout) mRootView).addView(mPopup, lp); } public void dismissPopup() { dismissPopup(true); } private void dismissPopup(boolean fullScreen) { if (fullScreen) { showUI(); mBlocker.setVisibility(View.VISIBLE); } setShowMenu(fullScreen); if (mPopup != null) { ((FrameLayout) mRootView).removeView(mPopup); mPopup = null; } mMenu.popupDismissed(); } public void onShowSwitcherPopup() { if (mPieRenderer != null && mPieRenderer.showsItems()) { mPieRenderer.hide(); } } private void setShowMenu(boolean show) { if (mOnScreenIndicators != null) { mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); } if (mMenuButton != null) { mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE); } } public boolean collapseCameraControls() { // Remove all the popups/dialog boxes boolean ret = false; if (mPopup != null) { dismissPopup(); ret = true; } onShowSwitcherPopup(); return ret; } protected void showPostCaptureAlert() { mOnScreenIndicators.setVisibility(View.GONE); mMenuButton.setVisibility(View.GONE); Util.fadeIn(mReviewDoneButton); mShutterButton.setVisibility(View.INVISIBLE); Util.fadeIn(mReviewRetakeButton); pauseFaceDetection(); } protected void hidePostCaptureAlert() { mOnScreenIndicators.setVisibility(View.VISIBLE); mMenuButton.setVisibility(View.VISIBLE); Util.fadeOut(mReviewDoneButton); mShutterButton.setVisibility(View.VISIBLE); Util.fadeOut(mReviewRetakeButton); resumeFaceDetection(); } public void setDisplayOrientation(int orientation) { if (mFaceView != null) { mFaceView.setDisplayOrientation(orientation); } } // shutter button handling public boolean isShutterPressed() { return mShutterButton.isPressed(); } public void enableShutter(boolean enabled) { if (mShutterButton != null) { mShutterButton.setEnabled(enabled); } } public void pressShutterButton() { if (mShutterButton.isInTouchMode()) { mShutterButton.requestFocusFromTouch(); } else { mShutterButton.requestFocus(); } mShutterButton.setPressed(true); } private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { @Override public void onZoomValueChanged(int index) { int newZoom = mController.onZoomChanged(index); if (mZoomRenderer != null) { mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom)); } } @Override public void onZoomStart() { if (mPieRenderer != null) { mPieRenderer.setBlockFocus(true); } } @Override public void onZoomEnd() { if (mPieRenderer != null) { mPieRenderer.setBlockFocus(false); } } } @Override public void onPieOpened(int centerX, int centerY) { setSwipingEnabled(false); dismissPopup(); if (mFaceView != null) { mFaceView.setBlockDraw(true); } } @Override public void onPieClosed() { setSwipingEnabled(true); if (mFaceView != null) { mFaceView.setBlockDraw(false); } } public void setSwipingEnabled(boolean enable) { mActivity.setSwipingEnabled(enable); } public Object getSurfaceTexture() { synchronized (mLock) { if (mSurfaceTexture == null) { try { mLock.wait(); } catch (InterruptedException e) { Log.w(TAG, "Unexpected interruption when waiting to get surface texture"); } } } return mSurfaceTexture; } // Countdown timer private void initializeCountDown() { mActivity.getLayoutInflater().inflate(R.layout.count_down_to_capture, (ViewGroup) mRootView, true); mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture)); mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController); } public boolean isCountingDown() { return mCountDownView != null && mCountDownView.isCountingDown(); } public void cancelCountDown() { if (mCountDownView == null) return; mCountDownView.cancelCountDown(); } public void startCountDown(int sec, boolean playSound) { if (mCountDownView == null) initializeCountDown(); mCountDownView.startCountDown(sec, playSound); } public void showPreferencesToast() { if (mNotSelectableToast == null) { String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode); mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT); } mNotSelectableToast.show(); } public void onPause() { cancelCountDown(); // Clear UI. collapseCameraControls(); if (mFaceView != null) mFaceView.clear(); mPreviewWidth = 0; mPreviewHeight = 0; } // focus UI implementation private FocusIndicator getFocusIndicator() { return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer; } @Override public boolean hasFaces() { return (mFaceView != null && mFaceView.faceExists()); } public void clearFaces() { if (mFaceView != null) mFaceView.clear(); } @Override public void clearFocus() { FocusIndicator indicator = getFocusIndicator(); if (indicator != null) indicator.clear(); } @Override public void setFocusPosition(int x, int y) { mPieRenderer.setFocus(x, y); } @Override public void onFocusStarted() { getFocusIndicator().showStart(); } @Override public void onFocusSucceeded(boolean timeout) { getFocusIndicator().showSuccess(timeout); } @Override public void onFocusFailed(boolean timeout) { getFocusIndicator().showFail(timeout); } @Override public void pauseFaceDetection() { if (mFaceView != null) mFaceView.pause(); } @Override public void resumeFaceDetection() { if (mFaceView != null) mFaceView.resume(); } public void onStartFaceDetection(int orientation, boolean mirror) { mFaceView.clear(); mFaceView.setVisibility(View.VISIBLE); mFaceView.setDisplayOrientation(orientation); mFaceView.setMirror(mirror); mFaceView.resume(); } @Override public void onFaceDetection(Face[] faces, android.hardware.Camera camera) { mFaceView.setFaces(faces); } public void onDisplayChanged() { mCameraControls.checkLayoutFlip(); mController.updateCameraOrientation(); } }