/* Preview.java Copyright (c) 2014 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.host.recorder.camera; import android.content.Context; import android.graphics.Point; import android.hardware.Camera; import android.hardware.Camera.Size; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Toast; import org.deviceconnect.android.deviceplugin.host.BuildConfig; import org.deviceconnect.android.deviceplugin.host.recorder.HostDeviceRecorder; import org.deviceconnect.android.deviceplugin.host.R; import java.io.IOException; import java.util.List; /** * カメラのプレビューを表示するクラス. * * @author NTT DOCOMO, INC. */ @SuppressWarnings("deprecation") public class Preview extends ViewGroup implements SurfaceHolder.Callback { /** デバッグ用フラグ. */ private static final boolean DEBUG = BuildConfig.DEBUG; /** デバッグ用タグ. */ private static final String LOG_TAG = "Camera:Preview"; /** * プレビューの横幅の閾値を定義する. * <p> * これ以上の横幅のプレビューは設定させない。 */ private static final int THRESHOLD_WIDTH = 640; /** * プレビューの縦幅の閾値を定義する. * <p> * これ以上の縦幅のプレビューは設定させない。 */ private static final int THRESHOLD_HEIGHT = 480; /** プレビューを表示するSurfaceView. */ private SurfaceView mSurfaceView; /** SurfaceViewを一時的に保持するホルダー. */ private SurfaceHolder mHolder; /** プレビューのサイズ. */ private HostDeviceRecorder.PictureSize mPreviewSize; /** * プレビューのフォーマット. */ private int mPreviewFormat; /** カメラのインスタンス. */ private Camera mCamera; /** カメラID. */ private int mCameraId; /** * コンストラクタ. * * @param context このクラスが属するコンテキスト */ public Preview(final Context context) { super(context); mSurfaceView = new SurfaceView(context); addView(mSurfaceView); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = mSurfaceView.getHolder(); mHolder.addCallback(this); } /** * Preview. * @param context context * @param attrs attributes */ public Preview(final Context context, final AttributeSet attrs) { super(context, attrs); mSurfaceView = new SurfaceView(context); addView(mSurfaceView); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = mSurfaceView.getHolder(); mHolder.addCallback(this); } /** * カメラのインスタンスを設定する. * * @param cameraId カメラID * @param camera カメラのインスタンス */ public void setCamera(final int cameraId, final Camera camera) { mCameraId = cameraId; mCamera = camera; if (mCamera != null) { requestLayout(); } } /** * カメラのインスタンスを切り替えます. * * @param cameraId カメラID * @param camera 切り替えるカメラのインスタンス */ public void switchCamera(final int cameraId, final Camera camera) { setCamera(cameraId, camera); try { camera.setPreviewDisplay(mHolder); } catch (IOException exception) { if (DEBUG) { Log.e(LOG_TAG, "IOException caused by setPreviewDisplay()", exception); } } Camera.Parameters parameters = camera.getParameters(); if (mPreviewSize != null) { parameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); } else { Size size = parameters.getPreviewSize(); mPreviewSize = new HostDeviceRecorder.PictureSize(size.width, size.height); } requestLayout(); camera.setParameters(parameters); } @Override protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { // We purposely disregard child measurements because act as a // wrapper to a SurfaceView that centers the camera preview instead // of stretching it. final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); setMeasuredDimension(width, height); } @Override protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) { if (changed && getChildCount() > 0) { final View child = getChildAt(0); final int width = r - l; final int height = b - t; int previewWidth = width; int previewHeight = height; if (mPreviewSize != null) { previewWidth = mPreviewSize.getWidth(); previewHeight = mPreviewSize.getHeight(); } // Center the child SurfaceView within the parent. if (width * previewHeight > height * previewWidth) { final int scaledChildWidth = previewWidth * height / previewHeight; child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height); } else { final int scaledChildHeight = previewHeight * width / previewWidth; child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2); } } } @Override public void surfaceCreated(final SurfaceHolder holder) { // The Surface has been created, acquire the camera and tell it where // to draw. try { if (mCamera != null) { mCamera.setPreviewDisplay(holder); } } catch (IOException e) { if (DEBUG) { Log.e(LOG_TAG, "IOException caused by setPreviewDisplay()", e); } } } @Override public void surfaceDestroyed(final SurfaceHolder holder) { // Surface will be destroyed when we return, so stop the preview. if (mCamera != null) { mCamera.stopPreview(); } } @Override public void surfaceChanged(final SurfaceHolder holder, final int format, final int w, final int h) { // Now that the size is known, set up the camera parameters and begin // the preview. if (mCamera != null) { try { setCameraParam(); } catch (Exception e) { if (DEBUG) { e.printStackTrace(); } } } } public SurfaceHolder getHolder() { return mHolder; } private void setCameraParam() { int rot = getCameraDisplayOrientation(getContext()); if (DEBUG) { Log.i(LOG_TAG, "PreViewSize: " + mPreviewSize.getWidth() + ", " + mPreviewSize.getHeight()); } Camera.Parameters parameters = mCamera.getParameters(); String focusMode = parameters.getFocusMode(); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); try { mCamera.setParameters(parameters); } catch (Exception e) { Log.i(LOG_TAG, "Auto focus not support."); parameters.setFocusMode(focusMode); mCamera.setParameters(parameters); } parameters.setRotation(rot); try { mCamera.setParameters(parameters); } catch (Exception e) { Log.i(LOG_TAG, "Rotation not support."); } mCamera.setDisplayOrientation(rot); try { mCamera.setParameters(parameters); } catch (Exception e) { Log.i(LOG_TAG, "Display orientation not support."); } mCamera.startPreview(); mPreviewFormat = parameters.getPreviewFormat(); } /** * プレビューのフォーマットを取得する. * @return プレビューのフォーマット */ public int getPreviewFormat() { return mPreviewFormat; } /** * プレビューの横幅を取得する. * @return 横幅 */ public int getPreviewWidth() { return mPreviewSize.getWidth(); } /** * プレビューの縦幅を取得する. * @return 縦幅 */ public int getPreviewHeight() { return mPreviewSize.getHeight(); } /** * 最適なサイズを取得する. * <p> * 指定されたサイズに最適なものがない場合にはnullを返却する。 * </p> * @param sizes サイズ一覧 * @param w 横幅 * @param h 縦幅 * @return 最適なサイズ */ private Size getOptimalSize(final List<Size> sizes, final int w, final int h) { final double aspectTolerance = 0.1; double targetRatio = (double) w / h; if (sizes == null) { return null; } if (BuildConfig.DEBUG) { Log.i(LOG_TAG, "getOptimalSize: " + w + ", " + h); Log.i(LOG_TAG, "-------"); for (Size size : sizes) { Log.i(LOG_TAG, " PreviewSize: " + size.width + ", " + size.height); } Log.i(LOG_TAG, "-------"); } Size optimalSize = null; double minDiff = Double.MAX_VALUE; // Try to find an size match aspect ratio and size for (Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > aspectTolerance) { continue; } if (Math.abs(size.height - h) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - h); } } // Cannot find the one match the aspect ratio, ignore the requirement if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Size size : sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - h); } } } if (BuildConfig.DEBUG) { if (optimalSize != null) { Log.i(LOG_TAG, "OptimalSize: " + optimalSize.width + ", " + optimalSize.height); } } return optimalSize; } /** * 画面サイズを取得する. * @param context コンテキスト * @return 画面サイズ */ private Point getDisplaySize(final Context context) { WindowManager mgr = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); Point size = new Point(); mgr.getDefaultDisplay().getSize(size); if (size.x > THRESHOLD_WIDTH) { size.x = THRESHOLD_WIDTH; } if (size.y > THRESHOLD_HEIGHT) { size.y = THRESHOLD_HEIGHT; } return size; } /** * カメラの向きを取得する. * @param context コンテキスト * @return カメラの向き */ public int getCameraDisplayOrientation(final Context context) { Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(mCameraId, info); WindowManager windowMgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); int rotation = windowMgr.getDefaultDisplay().getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; } else { result = (info.orientation - degrees + 360) % 360; } return result; } /** * 写真撮影を開始する. * * @param callback callback. */ public void takePicture(final Camera.PictureCallback callback) { if (mCamera != null) { post(new Runnable() { @Override public void run() { mCamera.takePicture(mShutterCallback, null, callback); Toast.makeText(getContext(), R.string.shutter, Toast.LENGTH_SHORT).show(); } }); } } /** * シャッターコールバック. * <p> * - シャッター音を鳴らすために使用する。 */ private final Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() { @Override public void onShutter() { // NOP } }; }