org.deviceconnect.android.deviceplugin.host.camera.CameraOverlay.java Source code

Java tutorial

Introduction

Here is the source code for org.deviceconnect.android.deviceplugin.host.camera.CameraOverlay.java

Source

/*
 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();
    }
}