Java tutorial
/* CameraOverlay.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.camera; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.YuvImage; import android.hardware.Camera; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.ResultReceiver; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.Surface; import android.view.View; import android.view.WindowManager; import org.deviceconnect.android.activity.IntentHandlerActivity; import org.deviceconnect.android.activity.PermissionUtility; import org.deviceconnect.android.deviceplugin.host.BuildConfig; import org.deviceconnect.android.deviceplugin.host.HostDeviceRecorder; import org.deviceconnect.android.deviceplugin.host.HostDeviceService; import org.deviceconnect.android.deviceplugin.host.R; import org.deviceconnect.android.provider.FileManager; import java.io.ByteArrayOutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.Executors; import java.util.logging.Logger; /** * ????. * * @author NTT DOCOMO, INC. */ @SuppressWarnings("deprecation") public class CameraOverlay implements Camera.PreviewCallback, Camera.ErrorCallback { /** * ?. */ public static final String DELETE_PREVIEW_ACTION = "org.deviceconnect.android.deviceplugin.host.DELETE_PREVIEW"; /** * NotificationID. */ private static final int NOTIFICATION_ID = 1010; /** * ????. */ private static final int PERIOD_TAKE_PHOTO_WAIT = 1500; /** * JPEG?. */ private static final int JPEG_COMPRESS_QUALITY = 100; /** ????. */ private static final String FILENAME_PREFIX = "android_camera_"; /** ??. */ private static final String FILE_EXTENSION = ".png"; /** Default Maximum Frame Rate. */ private static final double DEFAULT_MAX_FPS = 10.0d; /** ?. */ private SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("yyyyMMdd_kkmmss", Locale.JAPAN); /** . */ private final Context mContext; /** ?. */ private final WindowManager mWinMgr; /** */ private final HandlerThread mWorkerThread; /** ?. */ private final Handler mHandler; /** Camera ID. */ private final int mCameraId; /** ?. */ private FileManager mFileMgr; /** ?. */ private Preview mPreview; /** ??. */ private Camera mCamera; private final Object mCameraLock = new Object(); /** ???. */ private MixedReplaceMediaServer mServer; private HostDeviceRecorder.PictureSize mPreviewSize; private HostDeviceRecorder.PictureSize mPictureSize; private long mLastFrameTime; private long mFrameInterval; private double mMaxFps; private int mFacingDirection = 1; private final Logger mLogger = Logger.getLogger("host.dplugin"); /** * . * <p/> * ?????Overlay???? */ private boolean mFinishFlag; /** * ??????. */ private BroadcastReceiver mOrientReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { if (mPreview == null) { return; } String action = intent.getAction(); if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { updatePosition(mPreview); } } }; /** * . * * @param context * @param cameraId Camera ID. */ public CameraOverlay(final Context context, final int cameraId) { mContext = context; mWinMgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mWorkerThread = new HandlerThread(getClass().getSimpleName()); mWorkerThread.start(); mHandler = new Handler(mWorkerThread.getLooper()); mCameraId = cameraId; mMaxFps = DEFAULT_MAX_FPS; setPreviewFrameRate(mMaxFps); } public void setFacingDirection(final int dir) { mFacingDirection = dir; } public HostDeviceRecorder.PictureSize getPictureSize() { return mPictureSize; } public void setPictureSize(final HostDeviceRecorder.PictureSize size) { mPictureSize = size; } public HostDeviceRecorder.PictureSize getPreviewSize() { return mPreviewSize; } public void setPreviewSize(final HostDeviceRecorder.PictureSize size) { mPreviewSize = size; } public void setPreviewFrameRate(final double max) { mMaxFps = max; mFrameInterval = (long) (1 / max) * 1000L; } public double getPreviewMaxFrameRate() { return mMaxFps; } @Override protected void finalize() throws Throwable { super.finalize(); mWorkerThread.quit(); } /** * MixedReplaceMediaServer?. * * @param server ?? */ public void setServer(final MixedReplaceMediaServer server) { mServer = server; } /** * FileManager?. * * @param mgr FileManager? */ public void setFileManager(final FileManager mgr) { mFileMgr = mgr; } /** * ?????????. * * @return ?????true????false */ public synchronized boolean isShow() { return mPreview != null; } /** * Overlay?. * @param callback Overlay???? */ public void show(@NonNull final Callback callback) { Executors.newSingleThreadExecutor().submit(new Runnable() { @Override public void run() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final ResultReceiver cameraCapabilityCallback = new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { try { if (resultCode == Activity.RESULT_OK) { showInternal(callback); } else { callback.onFail(); } } catch (Throwable throwable) { callback.onFail(); } } }; final ResultReceiver overlayDrawingCapabilityCallback = new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { try { if (resultCode == Activity.RESULT_OK) { checkCameraCapability(cameraCapabilityCallback); } else { callback.onFail(); } } catch (Throwable throwable) { callback.onFail(); } } }; checkOverlayDrawingCapability(overlayDrawingCapabilityCallback); } else { showInternal(callback); } } catch (final Throwable throwable) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { throw throwable; } }); } } }); } private synchronized void showInternal(@NonNull final Callback callback) { mHandler.post(new Runnable() { @Override public void run() { try { mPreview = new Preview(mContext); Point size = getDisplaySize(); int pt = (int) (5 * getScaledDensity()); WindowManager.LayoutParams l = new WindowManager.LayoutParams(pt, pt, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); l.x = -size.x / 2; l.y = -size.y / 2; mWinMgr.addView(mPreview, l); mCamera = Camera.open(mCameraId); setRequestedPictureSize(mCamera); setRequestedPreviewSize(mCamera); mPreview.switchCamera(mCameraId, mCamera); mCamera.setPreviewCallback(CameraOverlay.this); mCamera.setErrorCallback(CameraOverlay.this); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); mContext.registerReceiver(mOrientReceiver, filter); sendNotification(); callback.onSuccess(); } catch (Throwable t) { if (BuildConfig.DEBUG) { Log.w("Overlay", "", t); } callback.onFail(); } } }); } private void setRequestedPictureSize(final Camera camera) { HostDeviceRecorder.PictureSize currentSize = mPictureSize; if (camera != null && currentSize != null) { Camera.Parameters params = camera.getParameters(); params.setPictureSize(currentSize.getWidth(), currentSize.getHeight()); camera.setParameters(params); } } private void setRequestedPreviewSize(final Camera camera) { HostDeviceRecorder.PictureSize currentSize = mPreviewSize; if (camera != null && currentSize != null) { Camera.Parameters params = camera.getParameters(); params.setPreviewSize(currentSize.getWidth(), currentSize.getHeight()); camera.setParameters(params); } } /** * ??????. * @param resultReceiver ???? */ @TargetApi(23) private void checkOverlayDrawingCapability(@NonNull final ResultReceiver resultReceiver) { if (Settings.canDrawOverlays(mContext)) { resultReceiver.send(Activity.RESULT_OK, null); } else { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + mContext.getPackageName())); IntentHandlerActivity.startActivityForResult(mContext, intent, new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (Settings.canDrawOverlays(mContext)) { resultReceiver.send(Activity.RESULT_OK, null); } else { resultReceiver.send(Activity.RESULT_CANCELED, null); } } }); } } /** * ?????. * @param resultReceiver ???? */ private void checkCameraCapability(@NonNull final ResultReceiver resultReceiver) { PermissionUtility.requestPermissions(mContext, new Handler(), new String[] { Manifest.permission.CAMERA }, new PermissionUtility.PermissionRequestCallback() { @Override public void onSuccess() { resultReceiver.send(Activity.RESULT_OK, null); } @Override public void onFail(@NonNull String deniedPermission) { resultReceiver.send(Activity.RESULT_CANCELED, null); } }); } /** * Overlay???. */ public void hide() { mHandler.post(new Runnable() { @Override public void run() { try { synchronized (mCameraLock) { mFinishFlag = false; hideNotification(); if (mCamera != null) { mPreview.setCamera(0, null); mCamera.stopPreview(); mCamera.setPreviewCallback(null); mCamera.release(); mCamera = null; } if (mPreview != null) { mWinMgr.removeView(mPreview); mPreview = null; } mContext.unregisterReceiver(mOrientReceiver); } } catch (Throwable t) { if (BuildConfig.DEBUG) { Log.w("Overlay", "", t); } } } }); } /** * Notification?. */ private void hideNotification() { NotificationManager manager = (NotificationManager) mContext.getSystemService(Service.NOTIFICATION_SERVICE); manager.cancel(NOTIFICATION_ID); } /** * Notification??. */ private void sendNotification() { PendingIntent contentIntent = createPendingIntent(); Notification notification = createNotification(contentIntent); notification.flags = Notification.FLAG_NO_CLEAR; NotificationManager manager = (NotificationManager) mContext.getSystemService(Service.NOTIFICATION_SERVICE); manager.cancel(NOTIFICATION_ID); manager.notify(NOTIFICATION_ID, notification); } /** * Notification??. * @param pendingIntent Notification????????Intent * @return Notification */ private Notification createNotification(final PendingIntent pendingIntent) { NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext.getApplicationContext()); builder.setContentIntent(pendingIntent); builder.setTicker(mContext.getString(R.string.overlay_preview_ticker)); builder.setSmallIcon(R.drawable.dconnect_icon); builder.setContentTitle(mContext.getString(R.string.overlay_preview_content_title)); builder.setContentText(mContext.getString(R.string.overlay_preview_content_message)); builder.setWhen(System.currentTimeMillis()); builder.setAutoCancel(false); return builder.build(); } /** * PendingIntent??. * @return PendingIntent */ private PendingIntent createPendingIntent() { Intent intent = new Intent(mContext, HostDeviceService.class); intent.setAction(DELETE_PREVIEW_ACTION); return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** * ?Overlay????. * * @param flag true??????? */ public synchronized void setFinishFlag(final boolean flag) { mFinishFlag = flag; } /** * ?. * <p> * ???listener?? * </p> * @param listener ?? */ public void takePicture(final OnTakePhotoListener listener) { mHandler.postDelayed(new Runnable() { @Override public void run() { takePictureInternal(listener); } }, PERIOD_TAKE_PHOTO_WAIT); } /** * ????. */ private void common() { synchronized (CameraOverlay.this) { if (mFinishFlag) { hide(); } else if (mCamera != null) { mCamera.stopPreview(); } } } /** * ?. * * @param listener ?? */ private void takePictureInternal(final OnTakePhotoListener listener) { synchronized (mCameraLock) { if (mPreview == null || mCamera == null) { if (listener != null) { listener.onFailedTakePhoto(); } return; } mPreview.takePicture(new Camera.PictureCallback() { @Override public void onPictureTaken(final byte[] data, final Camera camera) { if (data == null) { listener.onFailedTakePhoto(); common(); return; } int degrees = 0; switch (mWinMgr.getDefaultDisplay().getRotation()) { 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; } mLogger.info("takePicture: display rotation = " + degrees); int rotation = mFacingDirection == 1 ? 0 : degrees + 180; Bitmap original = BitmapFactory.decodeByteArray(data, 0, data.length); Bitmap rotated; if (rotation == 0) { rotated = original; } else { Matrix m = new Matrix(); m.setRotate(rotation); rotated = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), m, true); original.recycle(); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); rotated.compress(CompressFormat.JPEG, JPEG_COMPRESS_QUALITY, baos); byte[] jpeg = baos.toByteArray(); rotated.recycle(); mFileMgr.saveFile(createNewFileName(), jpeg, new FileManager.SaveFileCallback() { @Override public void onSuccess(@NonNull final String uri) { String filePath = mFileMgr.getBasePath().getAbsolutePath() + "/" + uri; if (listener != null) { listener.onTakenPhoto(uri, filePath); } common(); } @Override public void onFail(@NonNull final Throwable throwable) { if (listener != null) { listener.onFailedTakePhoto(); } common(); } }); } }); } } /** * Display???. * * @return */ private float getScaledDensity() { DisplayMetrics metrics = new DisplayMetrics(); mWinMgr.getDefaultDisplay().getMetrics(metrics); return metrics.scaledDensity; } /** * Display???. * * @return */ private Point getDisplaySize() { Display disp = mWinMgr.getDefaultDisplay(); Point size = new Point(); disp.getSize(size); return size; } /** * View?????. * * @param view ?View */ private void updatePosition(final View view) { if (view == null) { return; } Point size = getDisplaySize(); final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams(); lp.x = -size.x / 2; lp.y = -size.y / 2; view.post(new Runnable() { @Override public void run() { mWinMgr.updateViewLayout(view, lp); } }); } /** * ??????. * * @return ?? */ private String createNewFileName() { return FILENAME_PREFIX + mSimpleDateFormat.format(new Date()) + FILE_EXTENSION; } @Override public void onError(final int error, final Camera camera) { if (BuildConfig.DEBUG) { Log.w("Overlay", "onError: " + error); } hide(); } @Override public void onPreviewFrame(final byte[] data, final Camera camera) { synchronized (mCameraLock) { final long currentTime = System.currentTimeMillis(); if (mLastFrameTime != 0) { if ((currentTime - mLastFrameTime) < mFrameInterval) { mLastFrameTime = currentTime; return; } } if (mCamera != null && mCamera.equals(camera)) { mCamera.setPreviewCallback(null); if (mServer != null) { int format = mPreview.getPreviewFormat(); int width = mPreview.getPreviewWidth(); int height = mPreview.getPreviewHeight(); YuvImage yuvimage = new YuvImage(data, format, width, height, null); Rect rect = new Rect(0, 0, width, height); ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (yuvimage.compressToJpeg(rect, JPEG_COMPRESS_QUALITY, baos)) { byte[] jdata = baos.toByteArray(); int degree = mPreview.getCameraDisplayOrientation(mContext); if (degree == 0) { mServer.offerMedia(jdata); } else { BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options(); bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bmp = BitmapFactory.decodeByteArray(jdata, 0, jdata.length, bitmapFactoryOptions); if (bmp != null) { Matrix m = new Matrix(); m.setRotate(degree * mFacingDirection); Bitmap rotatedBmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true); if (rotatedBmp != null) { baos.reset(); if (rotatedBmp.compress(CompressFormat.JPEG, JPEG_COMPRESS_QUALITY, baos)) { mServer.offerMedia(baos.toByteArray()); } rotatedBmp.recycle(); } bmp.recycle(); } } } } mCamera.setPreviewCallback(this); } mLastFrameTime = currentTime; } } /** * ??. */ public interface OnTakePhotoListener { /** * ?????URI?. * * @param uri URI * @param filePath file path. */ void onTakenPhoto(String uri, String filePath); /** * ??????. */ void onFailedTakePhoto(); } /** * Overlay????. */ public interface Callback { /** * ?????????. */ void onSuccess(); /** * ????????????. */ void onFail(); } }