diff --git a/camera/MultiCameraApplication/AndroidManifest.xml b/camera/MultiCameraApplication/AndroidManifest.xml index ef0784c..1516ff5 100644 --- a/camera/MultiCameraApplication/AndroidManifest.xml +++ b/camera/MultiCameraApplication/AndroidManifest.xml @@ -1,51 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java index 6a05ae0..6230aa4 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class BotmLeftCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -92,6 +96,11 @@ public class BotmLeftCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; + static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); @@ -151,11 +160,12 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { @@ -165,34 +175,45 @@ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { public void onSurfaceTextureUpdated(SurfaceTexture surface) {} }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (!((manager.getCameraIdList().length >= 3) && + (manager.getCameraIdList().length <= 4))) { + Log.e(TAG, "this camera is not connected "); + return; + } cameraId = manager.getCameraIdList()[2]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); + + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); } - mMediaRecorder = new MediaRecorder(); + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -213,25 +234,50 @@ public void onOpened(CameraDevice camera) { } @Override public void onDisconnected(CameraDevice camera) { - cameraDevice.close(); + Log.e(TAG, "onDisconnected"); + closeCamera(); } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; - + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); + settings.getString("capture_list", "640x480")); String videoQuality = settings.getString("video_list", "medium"); int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); @@ -282,6 +328,7 @@ public void closeCamera() { imageReader = null; } if (null != mMediaRecorder) { + mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; @@ -293,7 +340,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera-3"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -329,6 +376,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -336,9 +397,11 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( settings.getString("capture_list", "640x480")); - + Log.d(TAG, "Selected imageDimension" + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); @@ -351,17 +414,7 @@ protected void takePicture() { CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -448,11 +501,18 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -507,9 +567,10 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -521,16 +582,33 @@ private void setUpMediaRecorder() throws IOException { Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); - + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; throw new RuntimeException(ex); } } @@ -549,6 +627,8 @@ private void stopRecordingVideo() { // Stop recording mMediaRecorder.stop(); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java index 6fec868..e5ba66d 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class BotmRightCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -92,6 +96,11 @@ public class BotmRightCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; + static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); @@ -151,11 +160,12 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { @@ -165,34 +175,43 @@ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { public void onSurfaceTextureUpdated(SurfaceTexture surface) {} }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (manager.getCameraIdList().length != 4) { + Log.e(TAG, "this camera is not connected "); + return; + } + cameraId = manager.getCameraIdList()[3]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; - } - mMediaRecorder = new MediaRecorder(); + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); + } + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -218,20 +237,45 @@ public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); + settings.getString("capture_list", "640x480")); String videoQuality = settings.getString("video_list", "medium"); int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); @@ -283,6 +327,7 @@ public void closeCamera() { imageReader = null; } if (null != mMediaRecorder) { + mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; @@ -294,7 +339,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera-4"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -330,6 +375,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -337,9 +396,11 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( settings.getString("capture_list", "640x480")); - + Log.d(TAG, "Selected imageDimension" + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); @@ -352,17 +413,7 @@ protected void takePicture() { CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -449,11 +500,18 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -508,9 +566,10 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -522,16 +581,33 @@ private void setUpMediaRecorder() throws IOException { Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); - + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; throw new RuntimeException(ex); } } @@ -550,6 +626,8 @@ private void stopRecordingVideo() { // Stop recording mMediaRecorder.stop(); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java index 30a040b..0e1c474 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +17,7 @@ package com.intel.multicamera; +import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -34,6 +36,7 @@ import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @@ -52,6 +55,8 @@ public class MainActivity extends AppCompatActivity { private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private FrameLayout frameView0, frameView1, frameView2, frameView3; + private boolean mHasCriticalPermissions = false; + TopLeftCam mTopLeftCam; TopRightCam mTopRightCam; BotmLeftCam mBotmLeftCam; @@ -71,12 +76,19 @@ protected void onCreate(Bundle savedInstanceState) { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); + checkPermissions(); + if (!mHasCriticalPermissions) { + Log.v(TAG, "onCreate: Missing critical permissions."); + finish(); + return; + } + setVisibilityFrameLayout(); } public void Open_TopLeftCam() { mTopLeftCam_textureView = findViewById(R.id.textureview0); - assert mTopLeftCam_textureView != null; + if (mTopLeftCam_textureView == null) return; mTopLeftCam_PictureButton = findViewById(R.id.Picture0); mTopLeftCam_RecordButton = findViewById(R.id.Record0); @@ -92,7 +104,7 @@ else if (isImageCaptureIntent()) public void Open_TopRightCam() { mTopRightCam_textureView = findViewById(R.id.textureview1); - assert mTopRightCam_textureView != null; + if (mTopRightCam_textureView == null) return; mTopRightCam_PictureButton = findViewById(R.id.Picture1); mTopRightCam_RecordButton = findViewById(R.id.Record1); @@ -103,7 +115,7 @@ public void Open_TopRightCam() { public void Open_BotmLeftCam() { mBotmLeftCam_textureView = findViewById(R.id.textureview2); - assert mTopRightCam_textureView != null; + if (mBotmLeftCam_textureView == null) return; mBotmLeftCam_PictureButton = findViewById(R.id.Picture2); mBotmLeftCam_RecordButton = findViewById(R.id.Record2); @@ -114,7 +126,7 @@ public void Open_BotmLeftCam() { public void Open_BotmRightCam() { mBotmRightCam_textureView = findViewById(R.id.textureview3); - assert mTopRightCam_textureView != null; + if (mBotmRightCam_textureView == null) return; mBotmRightCam_PictureButton = findViewById(R.id.Picture3); mBotmRightCam_RecordButton = findViewById(R.id.Record3); @@ -200,26 +212,121 @@ private void manageTopLeftCam() { frameView0.setVisibility(FrameLayout.VISIBLE); } else if (mTopLeftCam_textureView == null) { mTopLeftCam_textureView = findViewById(R.id.textureview0); - assert mTopLeftCam_textureView != null; + if (mTopLeftCam_textureView == null) return; } if (mTopLeftCam_textureView.isAvailable()) { frameView0.setVisibility(FrameLayout.VISIBLE); - mTopLeftCam.openCamera(); + mTopLeftCam.openCamera(mTopLeftCam_textureView.getWidth(), + mTopLeftCam_textureView.getHeight()); } else { mTopLeftCam_textureView.setSurfaceTextureListener(mTopLeftCam.textureListener); } } + private void manageTopRightCam() { + if (mTopRightCam == null) { + Open_TopRightCam(); + frameView1.setVisibility(FrameLayout.VISIBLE); + + } else if (mTopRightCam_textureView == null) { + mTopRightCam_textureView = findViewById(R.id.textureview1); + if (mTopRightCam_textureView == null) return; + } + + if (mTopRightCam_textureView.isAvailable()) { + frameView1.setVisibility(FrameLayout.VISIBLE); + mTopRightCam.openCamera(mTopRightCam_textureView.getWidth(), + mTopRightCam_textureView.getHeight()); + } else { + mTopRightCam_textureView.setSurfaceTextureListener(mTopRightCam.textureListener); + } + } + + private void manageBotmLeftCam() { + if (mBotmLeftCam == null) { + Open_BotmLeftCam(); + frameView2.setVisibility(FrameLayout.VISIBLE); + + } else if (mBotmLeftCam_textureView == null) { + mBotmLeftCam_textureView = findViewById(R.id.textureview2); + if (mBotmLeftCam_textureView == null) return; + } + + if (mBotmLeftCam_textureView.isAvailable()) { + frameView2.setVisibility(FrameLayout.VISIBLE); + mBotmLeftCam.openCamera(mBotmLeftCam_textureView.getWidth(), + mBotmLeftCam_textureView.getHeight()); + } else { + mBotmLeftCam_textureView.setSurfaceTextureListener(mBotmLeftCam.textureListener); + } + } + + private void manageBotmRightCam() { + if (mBotmRightCam == null) { + Open_BotmRightCam(); + frameView3.setVisibility(FrameLayout.VISIBLE); + + } else if (mBotmRightCam_textureView == null) { + mBotmRightCam_textureView = findViewById(R.id.textureview3); + if (mBotmRightCam_textureView == null) return; + } + + if (mBotmRightCam_textureView.isAvailable()) { + frameView3.setVisibility(FrameLayout.VISIBLE); + mBotmRightCam.openCamera(mBotmRightCam_textureView.getWidth(), + mBotmRightCam_textureView.getHeight()); + } else { + mBotmRightCam_textureView.setSurfaceTextureListener(mBotmRightCam.textureListener); + } + } + + /** + * Checks if any of the needed Android runtime permissions are missing. + * If they are, then launch the permissions activity under one of the following conditions: + * a) The permissions dialogs have not run yet. We will ask for permission only once. + * b) If the missing permissions are critical to the app running, we will display a fatal error + * dialog. Critical permissions are: camera, microphone and storage. The app cannot run without + * them. Non-critical permission is location. + */ + private void checkPermissions() { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.CAMERA) == + PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.RECORD_AUDIO) == + PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(getApplicationContext(), + Manifest.permission.READ_EXTERNAL_STORAGE) == + PackageManager.PERMISSION_GRANTED) { + mHasCriticalPermissions = true; + } else { + mHasCriticalPermissions = false; + } + + if (!mHasCriticalPermissions) { + Intent intent = new Intent(this, PermissionsActivity.class); + startActivity(intent); + finish(); + } + } + @Override protected void onResume() { super.onResume(); Log.e(TAG, "onResume"); + checkPermissions(); + if (!mHasCriticalPermissions) { + Log.v(TAG, "onResume: Missing critical permissions."); + finish(); + return; + } + CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE); try { numOfCameras = manager.getCameraIdList().length; - Log.d(TAG, "Total Cameras: " + manager.getCameraIdList().length); + Log.d(TAG, "onResume Total Cameras: " + manager.getCameraIdList().length); } catch (CameraAccessException e) { e.printStackTrace(); } @@ -227,17 +334,17 @@ protected void onResume() { if (numOfCameras == 1) { manageTopLeftCam(); } else if (numOfCameras == 2) { - if (mTopLeftCam_textureView.isAvailable()) { - mTopLeftCam.openCamera(); - } else { - mTopLeftCam_textureView.setSurfaceTextureListener(mTopLeftCam.textureListener); - } - - if (mTopRightCam_textureView.isAvailable()) { - mTopRightCam.openCamera(); - } else { - mTopRightCam_textureView.setSurfaceTextureListener(mTopRightCam.textureListener); - } + manageTopLeftCam(); + manageTopRightCam(); + } else if (numOfCameras == 3) { + manageTopLeftCam(); + manageBotmLeftCam(); + manageTopRightCam(); + } else if (numOfCameras == 4) { + manageTopLeftCam(); + manageTopRightCam(); + manageBotmLeftCam(); + manageBotmRightCam(); } else { Log.d(TAG, "onResume No CAMERA CONNECTED"); frameView0.setVisibility(FrameLayout.INVISIBLE); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java new file mode 100644 index 0000000..0df96ac --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2014 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.intel.multicamera; + +import android.Manifest; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.*; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Window; +import android.view.WindowManager; +import androidx.core.app.ActivityCompat; +import androidx.preference.PreferenceManager; + + +/** + * Activity that shows permissions request dialogs and handles lack of critical permissions. + */ +public class PermissionsActivity extends QuickActivity { + private String TAG = "PermissionsActivity"; + + private static int PERMISSION_REQUEST_CODE = 1; + private static int RESULT_CODE_OK = 1; + private static int RESULT_CODE_FAILED = 2; + + private int mIndexPermissionRequestCamera; + private int mIndexPermissionRequestMicrophone; + private int mIndexPermissionRequestLocation; + private int mIndexPermissionRequestStorage; + private boolean mShouldRequestCameraPermission; + private boolean mShouldRequestMicrophonePermission; + private boolean mShouldRequestLocationPermission; + private boolean mShouldRequestStoragePermission; + private int mNumPermissionsToRequest; + private boolean mFlagHasCameraPermission; + private boolean mFlagHasMicrophonePermission; + private boolean mFlagHasStoragePermission; + + + /** + * Close activity when secure app passes lock screen or screen turns + * off. + + private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.v(TAG, "received intent, finishing: " + intent.getAction()); + finish(); + } + };*/ + + @Override + protected void onCreateTasks(Bundle savedInstanceState) { + setContentView(R.layout.permissions); + + // Filter for screen off so that we can finish permissions activity + // when screen is off. + //IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF); + //registerReceiver(mShutdownReceiver, filter_screen_off); + + Window win = getWindow(); + win.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + + @Override + protected void onResumeTasks() { + mNumPermissionsToRequest = 0; + checkPermissions(); + } + + @Override + protected void onDestroyTasks() { + Log.v(TAG, "onDestroy: unregistering receivers"); + + //unregisterReceiver(mShutdownReceiver); + } + + /** + * Package private conversion method to turn String storage format into + * booleans. + * + * @param value String to be converted to boolean + * @return boolean value of stored String + */ + public boolean convertToBoolean(String value) { + boolean ret=false; + + if (value.compareTo("false") == 0) { + ret = false; + Log.d(TAG,"####FALSE"); + + } else if (value.compareTo("true")==0) { + Log.d(TAG,"####TRUE"); + ret =true; + } + + return ret; + } + + /** + * Retrieve a setting's value as a String, manually specifiying + * a default value. + */ + public String getString(String key, String defaultValue) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + try { + return preferences.getString(key, defaultValue); + } catch (ClassCastException e) { + Log.w(TAG, "existing preference with invalid type, removing and returning default", e); + preferences.edit().remove(key).apply(); + return defaultValue; + } + } + + + + private void checkPermissions() { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + mNumPermissionsToRequest++; + mShouldRequestCameraPermission = true; + } else { + mFlagHasCameraPermission = true; + } + + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.RECORD_AUDIO) + != PackageManager.PERMISSION_GRANTED) { + mNumPermissionsToRequest++; + mShouldRequestMicrophonePermission = true; + } else { + mFlagHasMicrophonePermission = true; + } + + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + mNumPermissionsToRequest++; + mShouldRequestStoragePermission = true; + } else { + mFlagHasStoragePermission = true; + } + + if (mNumPermissionsToRequest != 0) { + if (!convertToBoolean(getString("pref_has_seen_permissions_dialogs", "false"))) { + + buildPermissionsRequest(); + } else { + // Permissions dialog has already been shown, or we're on + // lockscreen, and we're still missing permissions. + handlePermissionsFailure(); + } + } else { + handlePermissionsSuccess(); + } + } + + private void buildPermissionsRequest() { + String[] permissionsToRequest = new String[mNumPermissionsToRequest]; + int permissionsRequestIndex = 0; + + if (mShouldRequestCameraPermission) { + permissionsToRequest[permissionsRequestIndex] = Manifest.permission.CAMERA; + mIndexPermissionRequestCamera = permissionsRequestIndex; + permissionsRequestIndex++; + } + if (mShouldRequestMicrophonePermission) { + permissionsToRequest[permissionsRequestIndex] = Manifest.permission.RECORD_AUDIO; + mIndexPermissionRequestMicrophone = permissionsRequestIndex; + permissionsRequestIndex++; + } + if (mShouldRequestStoragePermission) { + permissionsToRequest[permissionsRequestIndex] = Manifest.permission.WRITE_EXTERNAL_STORAGE; + mIndexPermissionRequestStorage = permissionsRequestIndex; + permissionsRequestIndex++; + } + + + Log.v(TAG, "requestPermissions count: " + permissionsToRequest.length); + requestPermissions(permissionsToRequest, PERMISSION_REQUEST_CODE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + Log.v(TAG, "onPermissionsResult counts: " + permissions.length + ":" + grantResults.length); + + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + settings.edit().putString("pref_has_seen_permissions_dialogs","true").apply(); + + if (mShouldRequestCameraPermission) { + if (grantResults.length > 0 && grantResults[mIndexPermissionRequestCamera] == + PackageManager.PERMISSION_GRANTED) { + mFlagHasCameraPermission = true; + } else { + handlePermissionsFailure(); + } + } + if (mShouldRequestMicrophonePermission) { + if (grantResults.length > 0 && grantResults[mIndexPermissionRequestMicrophone] == + PackageManager.PERMISSION_GRANTED) { + mFlagHasMicrophonePermission = true; + } else { + handlePermissionsFailure(); + } + } + if (mShouldRequestStoragePermission) { + if (grantResults.length > 0 && grantResults[mIndexPermissionRequestStorage] == + PackageManager.PERMISSION_GRANTED) { + mFlagHasStoragePermission = true; + } else { + handlePermissionsFailure(); + } + } + + if (mFlagHasCameraPermission && mFlagHasMicrophonePermission && mFlagHasStoragePermission) { + handlePermissionsSuccess(); + }else { + // Permissions dialog has already been shown + // and we're still missing permissions. + handlePermissionsFailure(); + } + } + + private void handlePermissionsSuccess() { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } + + private void handlePermissionsFailure() { + new AlertDialog.Builder(this).setTitle(getResources().getString(R.string.camera_error_title)) + .setMessage(getResources().getString(R.string.error_permissions)) + .setCancelable(false) + .setOnKeyListener(new Dialog.OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + finish(); + } + return true; + } + }) + .setNegativeButton(getResources().getString(R.string.dialog_dismiss), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .show(); + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java new file mode 100644 index 0000000..f13ffc2 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2014 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.intel.multicamera; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Log; + + +/** + * Workaround for lockscreen double-onResume() bug: + *

+ * We track 3 startup situations: + *

    + *
  • Normal startup -- e.g. from GEL.
  • + *
  • Secure lock screen startup -- e.g. with a keycode.
  • + *
  • Non-secure lock screen startup -- e.g. with just a swipe.
  • + *
+ * The KeyguardManager service can be queried to determine which state we are in. + * If started from the lock screen, the activity may be quickly started, + * resumed, paused, stopped, and then started and resumed again. This is + * problematic for launch time from the lock screen because we typically open the + * camera in onResume() and close it in onPause(). These camera operations take + * a long time to complete. To workaround it, this class filters out + * high-frequency onResume()->onPause() sequences if the KeyguardManager + * indicates that we have started from the lock screen. + *

+ *

+ * Subclasses should override the appropriate on[Create|Start...]Tasks() method + * in place of the original. + *

+ *

+ * Sequences of onResume() followed quickly by onPause(), when the activity is + * started from a lockscreen will result in a quick no-op.
+ *

+ */ +public abstract class QuickActivity extends Activity { + private String TAG = "QuickActivity"; + + /** onResume tasks delay from secure lockscreen. */ + private static final long ON_RESUME_DELAY_SECURE_MILLIS = 30; + /** onResume tasks delay from non-secure lockscreen. */ + private static final long ON_RESUME_DELAY_NON_SECURE_MILLIS = 15; + + /** A reference to the main handler on which to run lifecycle methods. */ + private Handler mMainHandler; + + /** + * True if onResume tasks have been skipped, and made false again once they + * are executed within the onResume() method or from a delayed Runnable. + */ + private boolean mSkippedFirstOnResume = false; + + /** When application execution started in SystemClock.elapsedRealtimeNanos(). */ + protected long mExecutionStartNanoTime = 0; + /** Was this session started with onCreate(). */ + protected boolean mStartupOnCreate = false; + + /** + * A runnable for deferring tasks to be performed in onResume() if starting + * from the lockscreen. + */ + private final Runnable mOnResumeTasks = new Runnable() { + @Override + public void run() { + if (mSkippedFirstOnResume) { + Log.v(TAG, "delayed Runnable --> onResumeTasks()"); + // Doing the tasks, can set to false. + mSkippedFirstOnResume = false; + onResumeTasks(); + } + } + }; + + @Override + protected final void onNewIntent(Intent intent) { + logLifecycle("onNewIntent", true); + Log.v(TAG, "Intent Action = " + intent.getAction()); + setIntent(intent); + super.onNewIntent(intent); + onNewIntentTasks(intent); + logLifecycle("onNewIntent", false); + } + + @Override + protected final void onCreate(Bundle bundle) { + mExecutionStartNanoTime = SystemClock.elapsedRealtimeNanos(); + logLifecycle("onCreate", true); + mStartupOnCreate = true; + super.onCreate(bundle); + mMainHandler = new Handler(getMainLooper()); + onCreateTasks(bundle); + logLifecycle("onCreate", false); + } + + @Override + protected final void onStart() { + logLifecycle("onStart", true); + onStartTasks(); + super.onStart(); + logLifecycle("onStart", false); + } + + @Override + protected final void onResume() { + logLifecycle("onResume", true); + + // For lockscreen launch, there are two possible flows: + // 1. onPause() does not occur before mOnResumeTasks is executed: + // Runnable mOnResumeTasks sets mSkippedFirstOnResume to false + // 2. onPause() occurs within ON_RESUME_DELAY_*_MILLIS: + // a. Runnable mOnResumeTasks is removed + // b. onPauseTasks() is skipped, mSkippedFirstOnResume remains true + // c. next onResume() will immediately execute onResumeTasks() + // and set mSkippedFirstOnResume to false + + + mMainHandler.removeCallbacks(mOnResumeTasks); + if (mSkippedFirstOnResume == false) { + + long delay = mSkippedFirstOnResume ? ON_RESUME_DELAY_SECURE_MILLIS : + ON_RESUME_DELAY_NON_SECURE_MILLIS; + // Skipping onResumeTasks; set to true. + mSkippedFirstOnResume = true; + Log.v(TAG, "onResume() --> postDelayed(mOnResumeTasks," + delay + ")"); + mMainHandler.postDelayed(mOnResumeTasks, delay); + } else { + Log.v(TAG, "onResume --> onResumeTasks()"); + // Doing the tasks, can set to false. + mSkippedFirstOnResume = false; + onResumeTasks(); + } + super.onResume(); + logLifecycle("onResume", false); + } + + @Override + protected final void onPause() { + logLifecycle("onPause", true); + mMainHandler.removeCallbacks(mOnResumeTasks); + // Only run onPauseTasks if we have not skipped onResumeTasks in a + // first call to onResume. If we did skip onResumeTasks (note: we + // just killed any delayed Runnable), we also skip onPauseTasks to + // adhere to lifecycle state machine. + if (mSkippedFirstOnResume == false) { + Log.v(TAG, "onPause --> onPauseTasks()"); + onPauseTasks(); + } + super.onPause(); + mStartupOnCreate = false; + logLifecycle("onPause", false); + } + + @Override + protected final void onStop() { + if (isChangingConfigurations()) { + Log.v(TAG, "changing configurations"); + } + logLifecycle("onStop", true); + onStopTasks(); + super.onStop(); + logLifecycle("onStop", false); + } + + @Override + protected final void onRestart() { + logLifecycle("onRestart", true); + super.onRestart(); + // TODO Support onRestartTasks() and handle the workaround for that too. + logLifecycle("onRestart", false); + } + + @Override + protected final void onDestroy() { + logLifecycle("onDestroy", true); + onDestroyTasks(); + super.onDestroy(); + logLifecycle("onDestroy", false); + } + + private void logLifecycle(String methodName, boolean start) { + String prefix = start ? "START" : "END"; + Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString()); + } + + /** + * Subclasses should override this in place of {@link Activity#onNewIntent}. + */ + protected void onNewIntentTasks(Intent newIntent) { + } + + /** + * Subclasses should override this in place of {@link Activity#onCreate}. + */ + protected void onCreateTasks(Bundle savedInstanceState) { + } + + /** + * Subclasses should override this in place of {@link Activity#onStart}. + */ + protected void onStartTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onResume}. + */ + protected void onResumeTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onPause}. + */ + protected void onPauseTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onStop}. + */ + protected void onStopTasks() { + } + + /** + * Subclasses should override this in place of {@link Activity#onDestroy}. + */ + protected void onDestroyTasks() { + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java index 68f7231..879fcad 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java @@ -107,7 +107,7 @@ public static class SettingsFragment public static SparseArray sCachedSelectedVideoQualities = new SparseArray(3); private static String mPrefChangedKey = null; - static boolean isPrefChangedKeyChnaged = false; + static boolean isPrefChangedKeyChanged = false; /** The selected {@link CamcorderProfile} qualities. */ public static class SelectedVideoQualities { public int large = -1; @@ -150,7 +150,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names); - isPrefChangedKeyChnaged = false; + isPrefChangedKeyChanged = false; } @Override @@ -193,12 +193,11 @@ public void onPause() { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String Key) { setSummary(findPreference(Key)); mPrefChangedKey = Key; - isPrefChangedKeyChnaged = true; + isPrefChangedKeyChanged = true; } public static String getchangedPrefKey() { - if (isPrefChangedKeyChnaged == true) { - isPrefChangedKeyChnaged = false; + if (isPrefChangedKeyChanged == true) { return mPrefChangedKey; } else { return DEFAULT_KEY; diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java index 3233405..7d44c31 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class TopLeftCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -93,6 +97,10 @@ public class TopLeftCam { byte[] jpegLength; private boolean mIsVideoCaptureIntent, mIsImageCaptureIntent, mIsonDoneClicked; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; static { ORIENTATIONS.append(Surface.ROTATION_0, 90); @@ -201,11 +209,12 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { @@ -215,34 +224,46 @@ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { public void onSurfaceTextureUpdated(SurfaceTexture surface) {} }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (!((manager.getCameraIdList().length >= 1) && + (manager.getCameraIdList().length <= 4))) { + Log.e(TAG, "this camera is not connected "); + return; + } + cameraId = manager.getCameraIdList()[0]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); } - mMediaRecorder = new MediaRecorder(); + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -269,23 +290,47 @@ public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; int quality = -1; - + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); String videoQuality = settings.getString("video_list", "medium"); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); + settings.getString("capture_list", "640x480")); quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); @@ -336,6 +381,7 @@ public void closeCamera() { imageReader = null; } if (null != mMediaRecorder) { + mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; @@ -347,7 +393,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera_0"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -383,6 +429,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -390,11 +450,14 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( settings.getString("capture_list", "640x480")); + Log.d(TAG, "Selected imageDimension " + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( - imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); + imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); @@ -405,17 +468,7 @@ protected void takePicture() { CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -502,11 +555,19 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); + setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -561,10 +622,9 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } - + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -576,16 +636,33 @@ private void setUpMediaRecorder() throws IOException { Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); - + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; throw new RuntimeException(ex); } } @@ -604,6 +681,8 @@ private void stopRecordingVideo() { // Stop recording mMediaRecorder.stop(); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java index e0f824a..de027f9 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java @@ -1,4 +1,5 @@ /* + * Copyright 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ import android.content.*; import android.content.pm.PackageManager; import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.hardware.camera2.*; import android.hardware.camera2.params.StreamConfigurationMap; @@ -71,14 +74,15 @@ public class TopRightCam { protected CameraCaptureSession cameraCaptureSessions; protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension; + private Size imageDimension, previewSize; private ImageReader imageReader; private File file; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private final int PERMISSIONS_REQUEST_SNAPSHOT = 3; - private Handler mBackgroundHandler; private HandlerThread mBackgroundThread; + private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; + private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; + private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); + private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; /** @@ -92,6 +96,11 @@ public class TopRightCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + /** + * Orientation of the camera sensor + */ + private int mSensorOrientation; + static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); @@ -151,11 +160,12 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here - openCamera(); + openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Transform you image captured size according to the surface width and height + configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { @@ -165,35 +175,44 @@ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { public void onSurfaceTextureUpdated(SurfaceTexture surface) {} }; - public void openCamera() { + public void openCamera(int width, int height) { CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE); Log.e(TAG, "is camera open"); try { + if (!((manager.getCameraIdList().length >= 2) && + (manager.getCameraIdList().length <= 4))) { + Log.e(TAG, "this camera is not connected "); + return; + } cameraId = manager.getCameraIdList()[1]; Log.e(TAG, "is camera open ID" + cameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, - new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_CAMERA_PERMISSION); - return; - } + if (Key.compareTo("video_list") == 0) { + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); - mMediaRecorder = new MediaRecorder(); + mProfile = CamcorderProfile.get(0, quality); + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + configureTransform(width, height); + } else { + previewSize = SettingsActivity.SettingsFragment.sizeFromSettingString( + settings.getString("capture_list", "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + configureTransform(width, height); + } + + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + configureTransform(width, height); startBackgroundThread(); manager.openCamera(cameraId, stateCallback, null); @@ -219,21 +238,46 @@ public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { - cameraDevice.close(); - cameraDevice = null; + Log.e(TAG, "onError"); + closeCamera(); } }; + private void configureTransform(int viewWidth, int viewHeight) { + if (null == textureView || null == previewSize) { + return; + } + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max((float)viewHeight / previewSize.getHeight(), + (float)viewWidth / previewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + textureView.setTransform(matrix); + } + protected void createCameraPreview() { try { closePreviewSession(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); + settings.getString("capture_list", "640x480")); String videoQuality = settings.getString("video_list", "medium"); int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); @@ -285,6 +329,7 @@ public void closeCamera() { imageReader = null; } if (null != mMediaRecorder) { + mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; @@ -296,7 +341,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-$cameraId"); + mBackgroundThread = new HandlerThread("Camera_1"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -332,6 +377,20 @@ protected void updatePreview() { } } + /** + * Retrieves the JPEG orientation from the specified screen rotation. + * + * @param rotation The screen rotation. + * @return The JPEG orientation (one of 0, 90, 270, and 360) + */ + private int getOrientation(int rotation) { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; + } + protected void takePicture() { if (null == cameraDevice) { Log.e(TAG, "cameraDevice is null"); @@ -339,9 +398,11 @@ protected void takePicture() { } try { + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( settings.getString("capture_list", "640x480")); - + Log.d(TAG, "Selected imageDimension: " + imageDimension.getWidth() + + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); @@ -354,17 +415,7 @@ protected void takePicture() { CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); - - // Add permission for camera and let user grant the permission - if (ActivityCompat.checkSelfPermission(mActivity, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != - PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions( - mActivity, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_REQUEST_SNAPSHOT); - return; - } + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); if (fileDetails == null || fileDetails.length < 5) { @@ -451,11 +502,18 @@ public void onConfigureFailed(CameraCaptureSession session) {} /* Recording Start*/ private void startRecordingVideo() { - if (null == cameraDevice || !textureView.isAvailable() || null == mProfile) { + if (null == cameraDevice || !textureView.isAvailable()) { return; } try { closePreviewSession(); + settings = PreferenceManager.getDefaultSharedPreferences(mActivity); + String videoQuality = settings.getString("video_list", "medium"); + + int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); + + mProfile = CamcorderProfile.get(0, quality); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); if (texture == null) return; @@ -510,9 +568,10 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } + + mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - mMediaRecorder.setProfile(mProfile); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); if (fileDetails == null || fileDetails.length < 5) { @@ -524,16 +583,34 @@ private void setUpMediaRecorder() throws IOException { Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); /** * set output file in media recorder */ mMediaRecorder.setOutputFile(mVideoFilename); + mMediaRecorder.setVideoEncodingBitRate(10000000); + mMediaRecorder.setVideoFrameRate(30); + mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + + int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { + case SENSOR_ORIENTATION_DEFAULT_DEGREES: + mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); + break; + case SENSOR_ORIENTATION_INVERSE_DEGREES: + mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); + break; + } try { mMediaRecorder.prepare(); } catch (IOException ex) { Log.e(TAG, "prepare failed for " + mVideoFilename, ex); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; throw new RuntimeException(ex); } } @@ -552,6 +629,8 @@ private void stopRecordingVideo() { // Stop recording mMediaRecorder.stop(); mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; if (null != mActivity) { Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java index ae9e6f1..aa91fc0 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java @@ -184,8 +184,10 @@ public static ContentValues getContentValues(int type, String fileDetails[], int Long.valueOf(fileDetails[4]) / 1000); contentValue.put(MediaStore.Video.Media.MIME_TYPE, fileDetails[2]); contentValue.put(MediaStore.Video.Media.DATA, fileDetails[3]); - contentValue.put(MediaStore.MediaColumns.WIDTH, width); - contentValue.put(MediaStore.MediaColumns.HEIGHT, height); + contentValue.put(MediaStore.Video.Media.WIDTH, width); + contentValue.put(MediaStore.Video.Media.HEIGHT, height); + contentValue.put(MediaStore.Video.Media.RESOLUTION, + Integer.toString(width) + "x" + Integer.toString(height)); } return contentValue; } diff --git a/camera/MultiCameraApplication/res/layout/permissions.xml b/camera/MultiCameraApplication/res/layout/permissions.xml new file mode 100644 index 0000000..eec475b --- /dev/null +++ b/camera/MultiCameraApplication/res/layout/permissions.xml @@ -0,0 +1,21 @@ + + + diff --git a/camera/MultiCameraApplication/res/values/strings.xml b/camera/MultiCameraApplication/res/values/strings.xml index b5b36cd..b97c7ef 100644 --- a/camera/MultiCameraApplication/res/values/strings.xml +++ b/camera/MultiCameraApplication/res/values/strings.xml @@ -59,4 +59,14 @@ "'IMG'_yyyyMMdd_HHmmss" + + + Camera error + + + The app does not have critical permissions needed to run. Please check your permissions settings. + + + Dismiss +