diff --git a/camera/MultiCameraApplication/AndroidManifest.xml b/camera/MultiCameraApplication/AndroidManifest.xml index 3be0329..27c5f0d 100644 --- a/camera/MultiCameraApplication/AndroidManifest.xml +++ b/camera/MultiCameraApplication/AndroidManifest.xml @@ -9,7 +9,7 @@ - + + android:theme="@style/AppTheme" + android:requestLegacyExternalStorage="true"> - - + + + + + + + + + - - - + android:theme="@style/AppTheme.NoActionBar">"> - - - - - - - - + + + + + + + diff --git a/camera/MultiCameraApplication/ic_launcher-web.png b/camera/MultiCameraApplication/ic_launcher-web.png deleted file mode 100644 index 0c90ffe..0000000 Binary files a/camera/MultiCameraApplication/ic_launcher-web.png and /dev/null differ diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java b/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java index 12ce3b5..e99dac6 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java @@ -26,12 +26,15 @@ public class AutoFitTextureView extends TextureView { private int mRatioWidth = 0; private int mRatioHeight = 0; + public AutoFitTextureView(Context context) { this(context, null); } + public AutoFitTextureView(Context context, AttributeSet attrs) { this(context, attrs, 0); } + public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @@ -41,6 +44,7 @@ public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { * on the ratio calculated from the parameters. Note that the actual sizes of parameters * don't matter, that is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the * same result. + * * @param width Relative horizontal size * @param height Relative vertical size */ diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java deleted file mode 100644 index 3772ccd..0000000 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmRightCam.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright 2014 The Android Open Source Project - * Copyright (c) 2019 Intel Corporation. - * - * 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.annotation.SuppressLint; -import android.app.Activity; -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; -import android.media.CamcorderProfile; -import android.media.Image; -import android.media.ImageReader; -import android.media.MediaRecorder; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.provider.MediaStore; -import android.util.Log; -import android.util.Size; -import android.util.SparseIntArray; -import android.view.Surface; -import android.view.TextureView; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.preference.PreferenceManager; -import java.io.*; -import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class BotmRightCam { - Activity mActivity; - private static final String TAG = "BotmRightCam"; - private String mNextVideoAbsolutePath; - private CamcorderProfile mProfile; - /** - * An {@link AutoFitTextureView} for camera preview. - */ - private AutoFitTextureView textureView; - private Button takePictureButton, TakeVideoButton; - - private MediaRecorder mMediaRecorder; - private String cameraId; - protected CameraDevice cameraDevice; - protected CameraCaptureSession cameraCaptureSessions; - protected CaptureRequest captureRequest; - protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension, previewSize; - private ImageReader imageReader; - private File file; - 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; - /** - * Whether the app is recording video now - */ - private boolean mIsRecordingVideo; - - // The video file that the hardware camera is about to record into - // (or is recording into. - private String mVideoFilename, mPictureFilename; - 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); - ORIENTATIONS.append(Surface.ROTATION_180, 270); - ORIENTATIONS.append(Surface.ROTATION_270, 180); - } - - public BotmRightCam(Activity activity, AutoFitTextureView textureView, Button PictureButton, - Button RecordButton) { - Log.e(TAG, "constructor called"); - this.mActivity = activity; - this.textureView = textureView; - this.textureView.setSurfaceTextureListener(textureListener); - this.ClickListeners(PictureButton, RecordButton); - this.settings = PreferenceManager.getDefaultSharedPreferences(activity); - } - - public void ClickListeners(Button PictureButton, Button RecordButton) { - TakePicureOnClicked(PictureButton); - - StartVideoRecording(RecordButton); - } - - private void TakePicureOnClicked(Button PictureButton) { - takePictureButton = PictureButton; - if (takePictureButton == null) return; - - takePictureButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - takePicture(); - Utils.broadcastNewPicture(mActivity.getApplicationContext(), mCurrentPictureValues); - } - }); - } - - private void StartVideoRecording(Button RecordButton) { - TakeVideoButton = RecordButton; - TakeVideoButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - // Intent i = new Intent(HomeScreenActivity.this, CameraActivity.class); - System.out.println(" onCreate Record0"); - if (mIsRecordingVideo) { - stopRecordingVideo(); - Utils.broadcastNewVideo(mActivity.getApplicationContext(), mCurrentVideoValues); - takePictureButton.setVisibility(View.VISIBLE); - } else { - startRecordingVideo(); - takePictureButton.setVisibility(View.GONE); - } - } - }); - } - - TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - // open your camera here - 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) { - return false; - } - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - }; - - 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(); - - 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); - - } catch (CameraAccessException e) { - e.printStackTrace(); - } - Log.e(TAG, "openCamera X"); - } - - private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { - @Override - public void onOpened(CameraDevice camera) { - // This is called when the camera is open - Log.e(TAG, "onOpened"); - cameraDevice = camera; - createCameraPreview(); - } - @Override - public void onDisconnected(CameraDevice camera) { - Log.e(TAG, "onDisconnected"); - closeCamera(); - } - @Override - public void onError(CameraDevice camera, int error) { - 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")); - - 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); - - if (Key.compareTo("video_list") == 0) { - texture.setDefaultBufferSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); - } else { - texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()); - } - - Surface surface = new Surface(texture); - captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); - captureRequestBuilder.addTarget(surface); - cameraDevice.createCaptureSession( - Arrays.asList(surface), new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == cameraDevice) { - return; - } - // When the session is ready, we start displaying the preview. - cameraCaptureSessions = cameraCaptureSession; - updatePreview(); - } - @Override - public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { - Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) - .show(); - } - }, null); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - public void releaseMedia() { - if (null != mMediaRecorder) { - try { - mMediaRecorder.stop(); - } catch (IllegalStateException ex) { - Log.d(TAG, "Stop called before start"); - } - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - } - - public void closeCamera() { - closePreviewSession(); - if (null != cameraDevice) { - cameraDevice.close(); - cameraDevice = null; - } - if (null != imageReader) { - imageReader.close(); - imageReader = null; - } - releaseMedia(); - stopBackgroundThread(); - } - - /** - * Starts a background thread and its {@link Handler}. - */ - private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-4"); - mBackgroundThread.start(); - mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); - } - - /** - * Stops the background thread and its {@link Handler}. - */ - private void stopBackgroundThread() { - if (mBackgroundThread != null) { - mBackgroundThread.quitSafely(); - try { - mBackgroundThread.join(); - mBackgroundThread = null; - mBackgroundHandler = null; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - protected void updatePreview() { - if (null == cameraDevice) { - Log.e(TAG, "updatePreview error, return"); - } - captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - HandlerThread thread = new HandlerThread("Camera Preview"); - thread.start(); - Handler handler = new Handler(thread.getLooper()); - try { - cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, handler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - /** - * 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"); - return; - } - - 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); - outputSurfaces.add(reader.getSurface()); - outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); - captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureRequestBuilder.addTarget(reader.getSurface()); - captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, - CameraMetadata.CONTROL_MODE_AUTO); - // Orientation - int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); - - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); - if (fileDetails == null || fileDetails.length < 5) { - Log.e(TAG, "Invalid file details"); - return; - } - mPictureFilename = fileDetails[3]; - mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); - - file = new File(mPictureFilename); - - ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image image = null; - try { - image = reader.acquireLatestImage(); - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - jpegLength = bytes; - mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, - jpegLength); - - save(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (image != null) { - image.close(); - } - } - } - - private void save(byte[] bytes) throws IOException { - OutputStream output = null; - try { - output = new FileOutputStream(file); - output.write(bytes); - } finally { - if (null != output) { - output.close(); - } - } - } - }; - reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); - final CameraCaptureSession.CaptureCallback captureListener = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); - } - }; - cameraDevice.createCaptureSession( - outputSurfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - session.capture(captureRequestBuilder.build(), captureListener, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - @Override - public void onConfigureFailed(CameraCaptureSession session) { - } - }, mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - /* Recording Start*/ - private void startRecordingVideo() { - 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; - texture.setDefaultBufferSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); - - captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); - List surfaces = new ArrayList<>(); - - // Set up Surface for the camera preview - Surface previewSurface = new Surface(texture); - surfaces.add(previewSurface); - captureRequestBuilder.addTarget(previewSurface); - - // Set up Surface for the MediaRecorder - Surface recorderSurface = mMediaRecorder.getSurface(); - surfaces.add(recorderSurface); - captureRequestBuilder.addTarget(recorderSurface); - - // Start a capture session - // Once the session starts, we can update the UI and start recording - cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession camCaptureSession) { - cameraCaptureSessions = camCaptureSession; - updatePreview(); - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - // UI - TakeVideoButton.setText(R.string.stop); - mIsRecordingVideo = true; - - // Start recording - mMediaRecorder.start(); - } - }); - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - if (null != mActivity) { - Toast.makeText(mActivity, "Failed", Toast.LENGTH_SHORT).show(); - } - } - }, mBackgroundHandler); - } catch (CameraAccessException | IOException e) { - e.printStackTrace(); - } - } - - private void setUpMediaRecorder() throws IOException { - if (null == mActivity) { - return; - } - - mMediaRecorder = new MediaRecorder(); - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); - mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); - if (fileDetails == null || fileDetails.length < 5) { - Log.e(TAG, "Invalid file details"); - return; - } - mVideoFilename = fileDetails[3]; - mCurrentVideoValues = - 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); - releaseMedia(); - throw new RuntimeException(ex); - } - } - - private void closePreviewSession() { - System.out.println(" closePreviewSession"); - if (cameraCaptureSessions != null) { - cameraCaptureSessions.close(); - cameraCaptureSessions = null; - } - } - - private void stopRecordingVideo() { - mIsRecordingVideo = false; - TakeVideoButton.setText(R.string.record); - - // Stop recording - releaseMedia(); - - if (null != mActivity) { - Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); - Log.d(TAG, "Video saved: " + mVideoFilename); - } - mVideoFilename = null; - - createCameraPreview(); - } -} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/CameraBase.java similarity index 58% rename from camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java rename to camera/MultiCameraApplication/java/com/intel/multicamera/CameraBase.java index 1d7459d..43af603 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/BotmLeftCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/CameraBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,11 @@ package com.intel.multicamera; -import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; -import android.content.*; -import android.content.pm.PackageManager; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.graphics.Matrix; import android.graphics.RectF; @@ -33,9 +33,10 @@ import android.media.ImageReader; import android.media.MediaRecorder; import android.net.Uri; -import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.Message; +import android.os.SystemClock; import android.provider.MediaStore; import android.util.Log; import android.util.Size; @@ -43,36 +44,34 @@ import android.view.Surface; import android.view.TextureView; import android.view.View; -import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceManager; import java.io.*; import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; +import java.util.Optional; -public class BotmLeftCam { +public class CameraBase { Activity mActivity; - private static final String TAG = "BotmLeftCam"; - private String mNextVideoAbsolutePath; + private String TAG; private CamcorderProfile mProfile; /** * An {@link AutoFitTextureView} for camera preview. */ private AutoFitTextureView textureView; - private Button takePictureButton, TakeVideoButton; + private ImageButton FullScrn, SettingsView, takePictureButton, TakeVideoButton; private MediaRecorder mMediaRecorder; private String cameraId; protected CameraDevice cameraDevice; protected CameraCaptureSession cameraCaptureSessions; - protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; private Size imageDimension, previewSize; private ImageReader imageReader; @@ -85,6 +84,13 @@ public class BotmLeftCam { private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private SharedPreferences settings; + private FrameLayout frameView0; + private SurfaceTexture mSurfaceTexture; + private String Capture_Key, Video_key, SettingsKey; + private long mRecordingStartTime; + private boolean mRecordingTimeCountsDown = false; + private static final int MSG_UPDATE_RECORD_TIME = 5; + /** * Whether the app is recording video now */ @@ -95,6 +101,8 @@ public class BotmLeftCam { private String mVideoFilename, mPictureFilename; private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; + private final Handler mHandler; + private TextView mRecordingTimeView; /** * Orientation of the camera sensor @@ -108,23 +116,212 @@ public class BotmLeftCam { ORIENTATIONS.append(Surface.ROTATION_270, 180); } - public BotmLeftCam(Activity activity, AutoFitTextureView textureView, Button PictureButton, - Button RecordButton) { - Log.e(TAG, "constructor called"); + private RoundedThumbnailView mRoundedThumbnailView; + FrameLayout roundedThumbnailViewControlLayout; + + private Uri mCurrentUri; + private String[] VideofileDetails; + + public CameraBase(Activity activity, AutoFitTextureView mtextureView, ImageButton[] Button, + TextView RecordingTimeView, String[] data, + RoundedThumbnailView roundedThumbnailView) { this.mActivity = activity; - this.textureView = textureView; - this.textureView.setSurfaceTextureListener(textureListener); - this.ClickListeners(PictureButton, RecordButton); + this.textureView = mtextureView; + this.ClickListeners(Button[0], Button[1]); + SettingsView = Button[2]; + FullScrn = Button[3]; this.settings = PreferenceManager.getDefaultSharedPreferences(activity); + cameraId = data[1]; + TAG = data[0]; + Capture_Key = data[2]; + Video_key = data[3]; + SettingsKey = data[4]; + mHandler = new MainHandler(); + + mRecordingTimeView = RecordingTimeView; + + mRoundedThumbnailView = roundedThumbnailView; + + RoundedThumbnail_setOnClickListners(); + + roundedThumbnailViewControlLayout = mActivity.findViewById(R.id.control1); + + Log.e(TAG, "constructor called"); + } + + private void RoundedThumbnail_setOnClickListners() { + mRoundedThumbnailView.setCallback(new RoundedThumbnailView.Callback() { + ImageView preView; + + @Override + public void onHitStateFinished() { + ImageView preView; + ImageButton btnDelete, playButton, btnBack; + FrameLayout previewLayout; + + String mimeType = Utils.getMimeTypeFromURI(mActivity, mCurrentUri); + + previewLayout = mActivity.findViewById(R.id.previewLayout); + previewLayout.setVisibility(View.VISIBLE); + + btnDelete = mActivity.findViewById(R.id.control_delete); + playButton = mActivity.findViewById(R.id.play_button); + preView = mActivity.findViewById(R.id.preview); + btnBack = mActivity.findViewById(R.id.control_back); + + btnBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + FrameLayout previewLayout; + previewLayout = mActivity.findViewById(R.id.previewLayout); + previewLayout.setVisibility(View.GONE); + + mRoundedThumbnailView.hideThumbnail(); + } + }); + + btnDelete.setOnClickListener(new View.OnClickListener() { + ImageView preView; + + @Override + public void onClick(View v) { + preView = mActivity.findViewById(R.id.preview); + + Uri uri = mCurrentUri; + File file = new File(Utils.getRealPathFromURI(mActivity, uri)); + if (file.exists()) { + Log.e(TAG, " File Deleted "); + file.delete(); + preView.setImageResource(android.R.color.background_dark); + } + } + }); + + if (mimeType.compareTo("video/mp4") == 0) { + VideoPreview(playButton, preView); + + } else { + photoPreview(playButton, preView); + } + } + }); + } + + private void VideoPreview(ImageButton playButton, ImageView preView) { + final Optional bitmap = + Utils.getVideoThumbnail(mActivity.getContentResolver(), mCurrentUri); + + playButton.setVisibility(View.VISIBLE); + + playButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Utils.playVideo(mActivity, mCurrentUri, TAG); + } + }); + + preView.setVisibility(View.VISIBLE); + preView.setImageBitmap(bitmap.get()); + } + + private void photoPreview(ImageButton playButton, ImageView preView) { + Uri PhotoUri = mCurrentUri; + + preView.setVisibility(View.VISIBLE); + playButton.setVisibility(View.GONE); + preView.setImageURI(PhotoUri); } - public void ClickListeners(Button PictureButton, Button RecordButton) { + /** + * This Handler is used to post message back onto the main thread of the + * application. + */ + private class MainHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_RECORD_TIME: { + updateRecordingTime(); + break; + } + + default: + Log.v(TAG, "Unhandled message: " + msg.what); + break; + } + } + } + + private void updateRecordingTime() { + if (!mIsRecordingVideo) { + return; + } + long now = SystemClock.uptimeMillis(); + long delta = now - mRecordingStartTime; + long mMaxVideoDurationInMs; + mMaxVideoDurationInMs = Utils.getMaxVideoDuration(mActivity); + + // Starting a minute before reaching the max duration + // limit, we'll countdown the remaining time instead. + boolean countdownRemainingTime = + (mMaxVideoDurationInMs != 0 && delta >= mMaxVideoDurationInMs - 60000); + + long deltaAdjusted = delta; + if (countdownRemainingTime) { + deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999; + } + String text; + + long targetNextUpdateDelay; + + text = Utils.millisecondToTimeString(deltaAdjusted, false); + targetNextUpdateDelay = 1000; + + setRecordingTime(text); + + if (mRecordingTimeCountsDown != countdownRemainingTime) { + // Avoid setting the color on every update, do it only + // when it needs changing. + mRecordingTimeCountsDown = countdownRemainingTime; + + int color = mActivity.getResources().getColor(R.color.recording_time_remaining_text); + + setRecordingTimeTextColor(color); + } + + long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay); + } + + public void setRecordingTime(String text) { + mRecordingTimeView.setText(text); + } + + public void setRecordingTimeTextColor(int color) { + mRecordingTimeView.setTextColor(color); + } + + public void showRecordingUI(boolean recording) { + if (recording) { + mRecordingTimeView.setText(""); + mRecordingTimeView.setVisibility(View.VISIBLE); + mRecordingTimeView.announceForAccessibility( + mActivity.getResources().getString(R.string.video_recording_started)); + + } else { + mRecordingTimeView.announceForAccessibility( + mActivity.getResources().getString(R.string.video_recording_stopped)); + mRecordingTimeView.setVisibility(View.GONE); + } + } + + public void ClickListeners(ImageButton PictureButton, ImageButton RecordButton) { TakePicureOnClicked(PictureButton); StartVideoRecording(RecordButton); } - private void TakePicureOnClicked(Button PictureButton) { + private void TakePicureOnClicked(ImageButton PictureButton) { takePictureButton = PictureButton; if (takePictureButton == null) return; @@ -132,25 +329,26 @@ private void TakePicureOnClicked(Button PictureButton) { @Override public void onClick(View v) { takePicture(); - Utils.broadcastNewPicture(mActivity.getApplicationContext(), mCurrentPictureValues); } }); } - private void StartVideoRecording(Button RecordButton) { + private void StartVideoRecording(ImageButton RecordButton) { TakeVideoButton = RecordButton; + TakeVideoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - // Intent i = new Intent(HomeScreenActivity.this, CameraActivity.class); - System.out.println(" onCreate Record0"); if (mIsRecordingVideo) { stopRecordingVideo(); - Utils.broadcastNewVideo(mActivity.getApplicationContext(), mCurrentVideoValues); + showRecordingUI(mIsRecordingVideo); + TakeVideoButton.setImageResource(R.drawable.ic_capture_video); takePictureButton.setVisibility(View.VISIBLE); + SettingsView.setEnabled(true); + SettingsView.setImageAlpha(200); + } else { startRecordingVideo(); - takePictureButton.setVisibility(View.GONE); } } }); @@ -160,17 +358,21 @@ public void onClick(View view) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here + mSurfaceTexture = surface; 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) { return false; } + @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } @@ -178,39 +380,28 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { 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); if (map == null) return; - settings = PreferenceManager.getDefaultSharedPreferences(mActivity); - String Key = SettingsActivity.SettingsFragment.getchangedPrefKey(); - if (Key.compareTo("video_list") == 0) { - String videoQuality = settings.getString("video_list", "medium"); + String Key = GetChnagedPrefKey(); + + if (Key.compareTo(Capture_Key) == 0) { + previewSize = SettingsPrefUtil.sizeFromSettingString( + settings.getString(Capture_Key, "640x480")); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); + } else { + String videoQuality = settings.getString(Video_key, "medium"); - int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + int quality = SettingsPrefUtil.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); @@ -222,7 +413,7 @@ public void openCamera(int width, int height) { } catch (CameraAccessException e) { e.printStackTrace(); } - Log.e(TAG, "openCamera X"); + Log.e(TAG, "openCamera"); } private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @@ -231,20 +422,57 @@ public void onOpened(CameraDevice camera) { // This is called when the camera is open Log.e(TAG, "onOpened"); cameraDevice = camera; + Ui_Enable(true); createCameraPreview(); } + @Override public void onDisconnected(CameraDevice camera) { Log.e(TAG, "onDisconnected"); + frameView0 = mActivity.findViewById(R.id.control2); + frameView0.setVisibility(FrameLayout.INVISIBLE); closeCamera(); + Ui_Enable(false); } + @Override public void onError(CameraDevice camera, int error) { Log.e(TAG, "onError"); closeCamera(); + Ui_Enable(false); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + Log.e(TAG, "onClose"); + super.onClosed(camera); + SurfaceUtil.clear(mSurfaceTexture); + Ui_Enable(false); } }; + private void Ui_Enable(boolean flag) { + TakeVideoButton.setEnabled(flag); + + takePictureButton.setEnabled(flag); + + SettingsView.setEnabled(flag); + + FullScrn.setEnabled(flag); + + if (flag) { + TakeVideoButton.setImageResource(R.drawable.ic_capture_video); + takePictureButton.setImageResource(R.drawable.ic_capture_camera_normal); + SettingsView.setImageAlpha(200); + FullScrn.setImageAlpha(200); + } else { + TakeVideoButton.setImageResource(R.drawable.ic_capture_video_disabled); + takePictureButton.setImageResource(R.drawable.ic_capture_camera_disabled); + SettingsView.setImageAlpha(95); + FullScrn.setImageAlpha(95); + } + } + private void configureTransform(int viewWidth, int viewHeight) { if (null == textureView || null == previewSize) { return; @@ -252,7 +480,10 @@ private void configureTransform(int viewWidth, int viewHeight) { 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); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight + + "previewWidth: " + previewSize.getWidth() + + "previewHeight:" + previewSize.getHeight()); + RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); float centerX = viewRect.centerX(); float centerY = viewRect.centerY(); @@ -269,25 +500,68 @@ private void configureTransform(int viewWidth, int viewHeight) { textureView.setTransform(matrix); } + /** + * Retrieve a setting's value as a String, manually specifiying + * a default value. + */ + public String getString(String key, String defaultValue) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mActivity); + 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; + } + } + + public String GetChnagedPrefKey() { + String Key = null; + + switch (SettingsKey) { + case "pref_resolution": + Key = getString(SettingsKey, "capture_list"); + break; + case "pref_resolution_1": + Key = getString(SettingsKey, "capture_list_1"); + break; + case "pref_resolution_2": + Key = getString(SettingsKey, "capture_list_2"); + break; + case "pref_resolution_3": + Key = getString(SettingsKey, "capture_list_3"); + break; + default: + break; + } + + return Key; + } + 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")); - String videoQuality = settings.getString("video_list", "medium"); + String Key = GetChnagedPrefKey(); + + imageDimension = SettingsPrefUtil.sizeFromSettingString( + settings.getString(Capture_Key, "640x480")); + String videoQuality = settings.getString(Video_key, "medium"); - int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + int quality = SettingsPrefUtil.getVideoQuality(0, videoQuality); Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); mProfile = CamcorderProfile.get(0, quality); - if (Key.compareTo("video_list") == 0) { + + if (Key.compareTo(Video_key) == 0) { + previewSize = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight); texture.setDefaultBufferSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); } else { + previewSize = imageDimension; texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()); } @@ -307,8 +581,10 @@ public void onConfigured(CameraCaptureSession cameraCaptureSession) { cameraCaptureSessions = cameraCaptureSession; updatePreview(); } + @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + closeCamera(); Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) .show(); } @@ -318,19 +594,6 @@ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { } } - public void releaseMedia() { - if (null != mMediaRecorder) { - try { - mMediaRecorder.stop(); - } catch (IllegalStateException ex) { - Log.d(TAG, "Stop called before start"); - } - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - } - public void closeCamera() { closePreviewSession(); if (null != cameraDevice) { @@ -349,7 +612,7 @@ public void closeCamera() { * Starts a background thread and its {@link Handler}. */ private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera-3"); + mBackgroundThread = new HandlerThread("Camera_1"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } @@ -407,8 +670,8 @@ protected void takePicture() { try { settings = PreferenceManager.getDefaultSharedPreferences(mActivity); - imageDimension = SettingsActivity.SettingsFragment.sizeFromSettingString( - settings.getString("capture_list", "640x480")); + imageDimension = SettingsPrefUtil.sizeFromSettingString( + settings.getString(Capture_Key, "640x480")); Log.d(TAG, "Selected imageDimension" + imageDimension.getWidth() + imageDimension.getHeight()); ImageReader reader = ImageReader.newInstance( @@ -416,6 +679,10 @@ protected void takePicture() { List outputSurfaces = new ArrayList(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); + + textureView.getSurfaceTexture().setDefaultBufferSize(imageDimension.getWidth(), + imageDimension.getHeight()); + captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.addTarget(reader.getSurface()); @@ -423,6 +690,7 @@ protected void takePicture() { CameraMetadata.CONTROL_MODE_AUTO); // Orientation int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + final int mRotation = rotation; captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); @@ -431,9 +699,9 @@ protected void takePicture() { return; } mPictureFilename = fileDetails[3]; - mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); + mCurrentPictureValues = Utils.getContentValues( + Utils.MEDIA_TYPE_IMAGE, fileDetails, imageDimension.getWidth(), + imageDimension.getHeight(), 0, new File(mPictureFilename).length()); file = new File(mPictureFilename); @@ -483,7 +751,30 @@ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); + + Utils.broadcastNewPicture(mActivity.getApplicationContext(), + mCurrentPictureValues); + + mCurrentUri = Utils.getCurrentPictureUri(); + + mRoundedThumbnailView.startRevealThumbnailAnimation("photo taken"); + + final Optional bitmap = Utils.generateThumbnail( + file, roundedThumbnailViewControlLayout.getWidth(), + roundedThumbnailViewControlLayout.getMeasuredHeight()); + + if (bitmap.isPresent()) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mRoundedThumbnailView.setThumbnail( + bitmap.get(), getOrientation(mRotation)); + } + }); + + } else { + Log.e(TAG, "No bitmap image found: "); + } createCameraPreview(); } @@ -517,9 +808,9 @@ private void startRecordingVideo() { try { closePreviewSession(); settings = PreferenceManager.getDefaultSharedPreferences(mActivity); - String videoQuality = settings.getString("video_list", "medium"); + String videoQuality = settings.getString(Video_key, "medium"); - int quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); + int quality = SettingsPrefUtil.getVideoQuality(0, videoQuality); Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); mProfile = CamcorderProfile.get(0, quality); @@ -552,11 +843,17 @@ public void onConfigured(@NonNull CameraCaptureSession camCaptureSession) { @Override public void run() { // UI - TakeVideoButton.setText(R.string.stop); mIsRecordingVideo = true; // Start recording mMediaRecorder.start(); + mRecordingStartTime = SystemClock.uptimeMillis(); + takePictureButton.setVisibility(View.GONE); + SettingsView.setEnabled(false); + SettingsView.setImageAlpha(95); + showRecordingUI(mIsRecordingVideo); + TakeVideoButton.setImageResource(R.drawable.ic_stop_normal); + updateRecordingTime(); } }); } @@ -564,8 +861,10 @@ public void run() { @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { if (null != mActivity) { - Toast.makeText(mActivity, "Failed", Toast.LENGTH_SHORT).show(); + Toast.makeText(mActivity, "Recording Failed", Toast.LENGTH_SHORT).show(); } + + releaseMedia(); } }, mBackgroundHandler); } catch (CameraAccessException | IOException e) { @@ -582,21 +881,21 @@ private void setUpMediaRecorder() throws IOException { mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); - if (fileDetails == null || fileDetails.length < 5) { + VideofileDetails = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); + if (VideofileDetails == null || VideofileDetails.length < 5) { Log.e(TAG, "Invalid file details"); return; } - mVideoFilename = fileDetails[3]; - mCurrentVideoValues = - Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, - mProfile.videoFrameWidth, mProfile.videoFrameHeight); + + mVideoFilename = VideofileDetails[3]; + file = new File(mVideoFilename); 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); @@ -604,6 +903,7 @@ private void setUpMediaRecorder() throws IOException { mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + switch (mSensorOrientation) { case SENSOR_ORIENTATION_DEFAULT_DEGREES: mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); @@ -629,17 +929,55 @@ private void closePreviewSession() { } } + public void releaseMedia() { + if (null != mMediaRecorder) { + try { + mMediaRecorder.stop(); + } catch (IllegalStateException ex) { + Log.d(TAG, "Stop called before start"); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + + private void saveVideo() { + long duration = SystemClock.uptimeMillis() - mRecordingStartTime; + if (duration > 0) { + // + } else { + Log.w(TAG, "Video duration <= 0 : " + duration); + } + + mCurrentVideoValues = Utils.getContentValues( + Utils.MEDIA_TYPE_VIDEO, VideofileDetails, mProfile.videoFrameWidth, + mProfile.videoFrameHeight, duration, new File(mVideoFilename).length()); + + Utils.broadcastNewVideo(mActivity.getApplicationContext(), mCurrentVideoValues); + } + private void stopRecordingVideo() { + mHandler.removeMessages(MSG_UPDATE_RECORD_TIME); mIsRecordingVideo = false; - TakeVideoButton.setText(R.string.record); - // Stop recording releaseMedia(); - if (null != mActivity) { - Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); - Log.d(TAG, "Video saved: " + mVideoFilename); + saveVideo(); + + mCurrentUri = Utils.getCurrentVideoUri(); + + mRoundedThumbnailView.startRevealThumbnailAnimation("Video taken"); + + final Optional bitmap = + Utils.getVideoThumbnail(mActivity.getContentResolver(), mCurrentUri); + + if (bitmap.isPresent()) { + mRoundedThumbnailView.setThumbnail(bitmap.get(), 0); + } else { + Log.e(TAG, "No bitmap image found: "); } + mVideoFilename = null; createCameraPreview(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCamIntents.java similarity index 71% rename from camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java rename to camera/MultiCameraApplication/java/com/intel/multicamera/CtsCamIntents.java index 83c95d9..e888cbb 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/TopLeftCam.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCamIntents.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,9 @@ package com.intel.multicamera; -import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.*; -import android.content.pm.PackageManager; +import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.graphics.Matrix; import android.graphics.RectF; @@ -34,10 +32,11 @@ import android.media.MediaRecorder; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.SystemClock; import android.provider.MediaStore; import android.util.Log; import android.util.Size; @@ -45,38 +44,36 @@ import android.view.Surface; import android.view.TextureView; import android.view.View; -import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceManager; import java.io.*; import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; +import java.util.Optional; -public class TopLeftCam { +public class CtsCamIntents { Activity mActivity; - private static final String TAG = "TopLeftCam"; - private String mNextVideoAbsolutePath; + private static final String TAG = "CtsCamIntents"; private CamcorderProfile mProfile; /** * An {@link AutoFitTextureView} for camera preview. */ private AutoFitTextureView textureView; - private Button takePictureButton, TakeVideoButton; + private ImageView takePictureButton, TakeVideoButton; private MediaRecorder mMediaRecorder; private String cameraId; protected CameraDevice cameraDevice; protected CameraCaptureSession cameraCaptureSessions; - protected CaptureRequest captureRequest; protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension, previewSize; + private Size previewSize; private ImageReader imageReader; private File file; private Handler mBackgroundHandler; @@ -89,10 +86,19 @@ public class TopLeftCam { private SharedPreferences settings; private Uri mCurrentVideoUri = null; private ParcelFileDescriptor mVideoFileDescriptor = null; + private SurfaceTexture mSurfaceTexture; + private Surface mOutPutSurface; + private FrameLayout frameView0; + private long mRecordingStartTime; + private boolean mRecordingTimeCountsDown = false; + private static final int MSG_UPDATE_RECORD_TIME = 5; + private TextView mRecordingTimeView; + private final Handler mHandler; + /** * Whether the app is recording video now */ - private boolean mIsRecordingVideo, onDoneClicked; + private boolean mIsRecordingVideo; // The video file that the hardware camera is about to record into // (or is recording into. @@ -100,7 +106,7 @@ public class TopLeftCam { private ContentValues mCurrentVideoValues, mCurrentPictureValues; byte[] jpegLength; - private boolean mIsVideoCaptureIntent, mIsImageCaptureIntent, mIsonDoneClicked; + private boolean mIsVideoCaptureIntent, mIsImageCaptureIntent; /** * Orientation of the camera sensor */ @@ -113,21 +119,107 @@ public class TopLeftCam { ORIENTATIONS.append(Surface.ROTATION_270, 180); } - public TopLeftCam(Activity activity, AutoFitTextureView textureView, Button PictureButton, - Button RecordButton) { + private String[] VideofileDetails; + + public CtsCamIntents(Activity activity, AutoFitTextureView mtextureView, + ImageView PictureButton, ImageView RecordButton, + TextView RecordingTimeView) { Log.e(TAG, "constructor called"); this.mActivity = activity; - onDoneClicked = false; mIsRecordingVideo = false; mIsVideoCaptureIntent = isVideoCaptureIntent(); mIsImageCaptureIntent = isImageCaptureIntent(); - this.textureView = textureView; - this.textureView.setSurfaceTextureListener(textureListener); + this.textureView = mtextureView; this.ClickListeners(PictureButton, RecordButton); this.settings = PreferenceManager.getDefaultSharedPreferences(activity); + mRecordingTimeView = RecordingTimeView; + mHandler = new MainHandler(); + } + + /** + * This Handler is used to post message back onto the main thread of the + * application. + */ + private class MainHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_RECORD_TIME: { + updateRecordingTime(); + break; + } + + default: + Log.v(TAG, "Unhandled message: " + msg.what); + break; + } + } + } + + private void updateRecordingTime() { + if (!mIsRecordingVideo) { + return; + } + long now = SystemClock.uptimeMillis(); + long delta = now - mRecordingStartTime; + long mMaxVideoDurationInMs; + mMaxVideoDurationInMs = Utils.getMaxVideoDuration(mActivity); + + // Starting a minute before reaching the max duration + // limit, we'll countdown the remaining time instead. + boolean countdownRemainingTime = + (mMaxVideoDurationInMs != 0 && delta >= mMaxVideoDurationInMs - 60000); + + long deltaAdjusted = delta; + if (countdownRemainingTime) { + deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999; + } + String text; + + long targetNextUpdateDelay; + + text = Utils.millisecondToTimeString(deltaAdjusted, false); + targetNextUpdateDelay = 1000; + + setRecordingTime(text); + + if (mRecordingTimeCountsDown != countdownRemainingTime) { + // Avoid setting the color on every update, do it only + // when it needs changing. + mRecordingTimeCountsDown = countdownRemainingTime; + + int color = mActivity.getResources().getColor(R.color.recording_time_remaining_text); + + setRecordingTimeTextColor(color); + } + + long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_RECORD_TIME, actualNextUpdateDelay); + } + + public void setRecordingTime(String text) { + mRecordingTimeView.setText(text); + } + + public void setRecordingTimeTextColor(int color) { + mRecordingTimeView.setTextColor(color); + } + + public void showRecordingUI(boolean recording) { + if (recording) { + mRecordingTimeView.announceForAccessibility( + mActivity.getResources().getString(R.string.video_recording_stopped)); + mRecordingTimeView.setVisibility(View.GONE); + + } else { + mRecordingTimeView.setText(""); + mRecordingTimeView.setVisibility(View.VISIBLE); + mRecordingTimeView.announceForAccessibility( + mActivity.getResources().getString(R.string.video_recording_started)); + } } public boolean isVideoCaptureIntent() { @@ -140,73 +232,78 @@ public boolean isImageCaptureIntent() { return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)); } - public void ClickListeners(Button PictureButton, Button RecordButton) { + public void ClickListeners(ImageView PictureButton, ImageView RecordButton) { TakePictureOnClicked(PictureButton); StartVideoRecording(RecordButton); } - private void TakePictureOnClicked(Button PictureButton) { + private void TakePictureOnClicked(ImageView PictureButton) { takePictureButton = PictureButton; if (takePictureButton == null) return; takePictureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (!onDoneClicked) { - onDoneClicked = false; - - takePicture(); - - if (mIsImageCaptureIntent) { - onDoneClicked = true; - takePictureButton.setText(R.string.done); - } else { - Utils.broadcastNewPicture(mActivity.getApplicationContext(), - mCurrentPictureValues); - } + takePicture(); - } else if (mIsImageCaptureIntent) { + if (!mIsImageCaptureIntent) { + Utils.broadcastNewPicture(mActivity.getApplicationContext(), + mCurrentPictureValues); + } else { mIsImageCaptureIntent = false; - onDoneClicked = false; - onDoneClicked(); } } }); } - private void StartVideoRecording(Button RecordButton) { + private void StartVideoRecording(ImageView RecordButton) { TakeVideoButton = RecordButton; TakeVideoButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (mIsonDoneClicked) { - mIsonDoneClicked = false; - onDoneClicked(); - } else if (mIsRecordingVideo == true) { + if (mIsRecordingVideo == true) { + showRecordingUI(mIsRecordingVideo); stopRecordingVideo(); - if (!mIsVideoCaptureIntent) takePictureButton.setVisibility(View.VISIBLE); - if (mIsVideoCaptureIntent) { + FrameLayout previewLayout = + mActivity.findViewById(R.id.intentPreviewLayout); + previewLayout.setVisibility(View.VISIBLE); + VideoPreview((ImageView)mActivity.findViewById(R.id.IntentPreview)); + + ImageButton IntentDone = mActivity.findViewById(R.id.IntentDone); + + IntentDone.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + doReturnToCaller(true); + } + }); mIsVideoCaptureIntent = false; - TakeVideoButton.setText(R.string.done); - mIsonDoneClicked = true; - } else { - Utils.broadcastNewVideo(mActivity.getApplicationContext(), - mCurrentVideoValues); } } else if (mIsRecordingVideo == false) { + TakeVideoButton.setImageResource(R.drawable.ic_stop_normal); startRecordingVideo(); + showRecordingUI(mIsRecordingVideo); takePictureButton.setVisibility(View.GONE); } } }); } - public void onDoneClicked() { - doReturnToCaller(true); + private void VideoPreview(ImageView preView) { + final Optional bitmap = + Utils.getVideoThumbnail(mActivity.getContentResolver(), mCurrentVideoUri); + + preView.setVisibility(View.VISIBLE); + preView.setImageBitmap(bitmap.get()); + } + + private void photoPreview(ImageView preView, Uri PhotoUri) { + preView.setVisibility(View.VISIBLE); + preView.setImageURI(PhotoUri); } private void doReturnToCaller(boolean valid) { @@ -231,17 +328,23 @@ private void doReturnToCaller(boolean valid) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // open your camera here + mSurfaceTexture = surface; + // Surface mSurface = new Surface(mSurfaceTexture); + // mSurface.release(); 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) { return false; } + @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } @@ -249,40 +352,18 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { 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(); - - 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); + int total_psizes = map.getOutputSizes(ImageFormat.JPEG).length; - 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); - } + previewSize = map.getOutputSizes(SurfaceTexture.class)[total_psizes - 1]; + Log.d(TAG, "camera preview width: " + previewSize.getWidth() + + " preview height: " + previewSize.getHeight()); mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); @@ -294,7 +375,7 @@ public void openCamera(int width, int height) { } catch (CameraAccessException e) { e.printStackTrace(); } - Log.e(TAG, "openCamera X"); + Log.e(TAG, "openCamera"); } private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @@ -305,17 +386,28 @@ public void onOpened(CameraDevice camera) { cameraDevice = camera; createCameraPreview(); } + @Override public void onDisconnected(CameraDevice camera) { Log.e(TAG, "onDisconnected"); + frameView0 = mActivity.findViewById(R.id.control1); + frameView0.setVisibility(FrameLayout.INVISIBLE); closeCamera(); } + @Override public void onError(CameraDevice camera, int error) { Log.e(TAG, "onError"); closeCamera(); } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + Log.e(TAG, "onClose"); + super.onClosed(camera); + SurfaceUtil.clear(mSurfaceTexture); + } }; private void configureTransform(int viewWidth, int viewHeight) { @@ -325,7 +417,9 @@ private void configureTransform(int viewWidth, int viewHeight) { 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); + Log.e(TAG, "configureTransform() viewWidth: " + viewWidth + " viewHeight: " + viewHeight + + "previewWidth: " + previewSize.getWidth() + + "previewHeight:" + previewSize.getHeight()); RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); float centerX = viewRect.centerX(); float centerY = viewRect.centerY(); @@ -347,26 +441,11 @@ protected void createCameraPreview() { 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")); - - quality = SettingsActivity.SettingsFragment.getVideoQuality(0, videoQuality); - Log.d(TAG, "Selected video quality for '" + videoQuality + "' is " + quality); - - mProfile = CamcorderProfile.get(0, quality); - - if (Key.compareTo("video_list") == 0) { - texture.setDefaultBufferSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); - } else { - texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()); - } + texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface surface = new Surface(texture); + mOutPutSurface = surface; captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); @@ -382,8 +461,10 @@ public void onConfigured(CameraCaptureSession cameraCaptureSession) { cameraCaptureSessions = cameraCaptureSession; updatePreview(); } + @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + closeCamera(); Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) .show(); } @@ -393,19 +474,6 @@ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { } } - public void releaseMedia() { - if (null != mMediaRecorder) { - try { - mMediaRecorder.stop(); - } catch (IllegalStateException ex) { - Log.d(TAG, "Stop called before start"); - } - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - } - public void closeCamera() { closePreviewSession(); if (null != cameraDevice) { @@ -416,7 +484,9 @@ public void closeCamera() { imageReader.close(); imageReader = null; } - releaseMedia(); + if (null != mMediaRecorder) { + releaseMedia(); + } closeVideoFileDescriptor(); stopBackgroundThread(); } @@ -482,14 +552,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()); + Log.d(TAG, + "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight()); ImageReader reader = ImageReader.newInstance( - imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1); + previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1); List outputSurfaces = new ArrayList(2); outputSurfaces.add(reader.getSurface()); outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); @@ -508,10 +575,12 @@ protected void takePicture() { return; } mPictureFilename = fileDetails[3]; - mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); - + mCurrentPictureValues = Utils.getContentValues( + Utils.MEDIA_TYPE_IMAGE, fileDetails, previewSize.getWidth(), + previewSize.getHeight(), 0, new File(mPictureFilename).length()); + ContentResolver resolver = mActivity.getContentResolver(); + final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + new ContentValues(mCurrentPictureValues)); file = new File(mPictureFilename); ImageReader.OnImageAvailableListener readerListener = @@ -560,9 +629,27 @@ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + FrameLayout previewLayout = + mActivity.findViewById(R.id.intentPreviewLayout); + previewLayout.setVisibility(View.VISIBLE); + photoPreview( + (ImageView)mActivity.findViewById(R.id.IntentPreview), + uri); + + ImageButton IntentDone = + mActivity.findViewById(R.id.IntentDone); + + IntentDone.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + doReturnToCaller(true); + } + }); + } + }); } }; cameraDevice.createCaptureSession( @@ -593,13 +680,8 @@ private void startRecordingVideo() { } 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); + mProfile = CamcorderProfile.get(0, CamcorderProfile.QUALITY_HIGH); setUpMediaRecorder(); SurfaceTexture texture = textureView.getSurfaceTexture(); @@ -630,9 +712,9 @@ public void onConfigured(@NonNull CameraCaptureSession camCaptureSession) { @Override public void run() { // UI - TakeVideoButton.setText(R.string.stop); mIsRecordingVideo = true; - + mRecordingStartTime = SystemClock.uptimeMillis(); + updateRecordingTime(); // Start recording mMediaRecorder.start(); } @@ -644,6 +726,8 @@ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession if (null != mActivity) { Toast.makeText(mActivity, "Failed", Toast.LENGTH_SHORT).show(); } + + releaseMedia(); } }, mBackgroundHandler); } catch (CameraAccessException | IOException e) { @@ -655,22 +739,22 @@ private void setUpMediaRecorder() throws IOException { if (null == mActivity) { return; } - String result = null; ContentResolver mContentResolver = mActivity.getContentResolver(); Intent intent = mActivity.getIntent(); - Bundle extras = intent.getExtras(); + Bundle myExtras = intent.getExtras(); closeVideoFileDescriptor(); - if (mIsVideoCaptureIntent && extras != null) { - Uri saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT); + if (mIsVideoCaptureIntent && myExtras != null) { + Uri saveUri = myExtras.getParcelable(MediaStore.EXTRA_OUTPUT); if (saveUri != null) { try { mVideoFileDescriptor = mContentResolver.openFileDescriptor(saveUri, "rw"); mCurrentVideoUri = saveUri; mVideoFilename = Utils.getFileNameFromUri(saveUri); + } catch (java.io.FileNotFoundException ex) { // invalid uri Log.e(TAG, ex.toString()); @@ -684,25 +768,10 @@ private void setUpMediaRecorder() throws IOException { if (mVideoFileDescriptor != null) { mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor()); - mVideoFilename = "CtsCameraIntents.mp4"; - } else { - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); - if (fileDetails == null || fileDetails.length < 5) { - Log.e(TAG, "Invalid file details"); - return; - } - mVideoFilename = fileDetails[3]; - mCurrentVideoValues = - Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, fileDetails, - mProfile.videoFrameWidth, mProfile.videoFrameHeight); - - /** - * set output file in media recorder - */ - mMediaRecorder.setOutputFile(mVideoFilename); } mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mMediaRecorder.setVideoEncodingBitRate(10000000); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); @@ -735,17 +804,34 @@ private void closePreviewSession() { } } + private void saveVideo() { + long duration = SystemClock.uptimeMillis() - mRecordingStartTime; + if (duration > 0) { + // + } else { + Log.w(TAG, "Video duration <= 0 : " + duration); + } + } + + public void releaseMedia() { + if (null != mMediaRecorder) { + try { + mMediaRecorder.stop(); + } catch (IllegalStateException ex) { + Log.d(TAG, "Stop called before start"); + } + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + private void stopRecordingVideo() { + mHandler.removeMessages(MSG_UPDATE_RECORD_TIME); + mIsRecordingVideo = false; - TakeVideoButton.setText(R.string.record); - // Stop recording releaseMedia(); - - if (null != mActivity) { - Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); - Log.d(TAG, "Video saved: " + mVideoFilename); - } mVideoFilename = null; closeVideoFileDescriptor(); createCameraPreview(); diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCameraIntentsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCameraIntentsActivity.java new file mode 100644 index 0000000..30e1132 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCameraIntentsActivity.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 2019 Intel Corporation. + * + * 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.os.Bundle; +import android.provider.MediaStore; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; + +public class CtsCameraIntentsActivity extends AppCompatActivity { + private static final String TAG = "CameraFullSrnActivity"; + /** + * An {@link AutoFitTextureView} for camera preview. + */ + private AutoFitTextureView mCam_textureView; + + private ImageView mCam_PictureButton, mCam_RecordButton; + + private CtsCamIntents CamIntents; + + private TextView mRecordingTimeView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(TAG, "onCreate"); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.activity_multiview); + setContentView(R.layout.activity_itscameraintents); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + View decorView = getWindow().getDecorView(); + + int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; + decorView.setSystemUiVisibility(uiOptions); + + mCam_textureView = findViewById(R.id.textureview0); + if (mCam_textureView == null) return; + + mCam_PictureButton = findViewById(R.id.Picture0); + mCam_RecordButton = findViewById(R.id.Record0); + + Open_Cam(); + } + + public boolean isVideoCaptureIntent() { + String action = this.getIntent().getAction(); + ; + return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); + } + + public boolean isImageCaptureIntent() { + String action = this.getIntent().getAction(); + return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)); + } + + public void Open_Cam() { + this.setTitle("CtsCamIntents"); + + if (isVideoCaptureIntent()) + mCam_PictureButton.setVisibility(View.GONE); + else if (isImageCaptureIntent()) + mCam_RecordButton.setVisibility(View.GONE); + + mRecordingTimeView = findViewById(R.id.recording_time); + + CamIntents = new CtsCamIntents(this, mCam_textureView, mCam_PictureButton, + mCam_RecordButton, mRecordingTimeView); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Log.d(TAG, "onDestroy"); + } + + @Override + protected void onPause() { + super.onPause(); + Log.e(TAG, "onPause"); + closeCamera(); + } + + @Override + protected void onResume() { + super.onResume(); + Log.d(TAG, "onResume"); + + if (mCam_textureView.isAvailable()) { + CamIntents.textureListener.onSurfaceTextureAvailable( + mCam_textureView.getSurfaceTexture(), mCam_textureView.getWidth(), + mCam_textureView.getHeight()); + } else { + mCam_textureView.setSurfaceTextureListener(CamIntents.textureListener); + } + } + + private void closeCamera() { + if (null != CamIntents) CamIntents.closeCamera(); + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java deleted file mode 100644 index 1b85b06..0000000 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * Copyright (c) 2019 Intel Corporation. - * - * 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.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraManager; -import android.os.Bundle; -import android.provider.MediaStore; -import android.util.Log; -import android.util.SparseIntArray; -import android.view.Menu; -import android.view.MenuItem; -import android.view.Surface; -import android.view.View; -import android.widget.Button; -import android.widget.FrameLayout; -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"; - /** - * An {@link AutoFitTextureView} for camera preview. - */ - private AutoFitTextureView mTopLeftCam_textureView, mTopRightCam_textureView, - mBotmLeftCam_textureView, mBotmRightCam_textureView; - - private Button mTopLeftCam_PictureButton, mTopRightCam_PictureButton, - mBotmLeftCam_PictureButton, mBotmRightCam_PictureButton, mTopLeftCam_RecordButton, - mTopRightCam_RecordButton, mBotmLeftCam_RecordButton, mBotmRightCam_RecordButton; - - private int numOfCameras; - private static final int REQUEST_CAMERA_PERMISSION = 200; - private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); - private FrameLayout frameView0, frameView1, frameView2, frameView3; - - private boolean mHasCriticalPermissions = false; - - TopLeftCam mTopLeftCam; - TopRightCam mTopRightCam; - BotmLeftCam mBotmLeftCam; - BotmRightCam mBotmRightCam; - - static { - ORIENTATIONS.append(Surface.ROTATION_0, 90); - ORIENTATIONS.append(Surface.ROTATION_90, 0); - ORIENTATIONS.append(Surface.ROTATION_180, 270); - ORIENTATIONS.append(Surface.ROTATION_270, 180); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - 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); - if (mTopLeftCam_textureView == null) return; - - mTopLeftCam_PictureButton = findViewById(R.id.Picture0); - mTopLeftCam_RecordButton = findViewById(R.id.Record0); - - if (isVideoCaptureIntent()) - mTopLeftCam_PictureButton.setVisibility(View.GONE); - else if (isImageCaptureIntent()) - mTopLeftCam_RecordButton.setVisibility(View.GONE); - - mTopLeftCam = new TopLeftCam(MainActivity.this, mTopLeftCam_textureView, - mTopLeftCam_PictureButton, mTopLeftCam_RecordButton); - } - - public void Open_TopRightCam() { - mTopRightCam_textureView = findViewById(R.id.textureview1); - if (mTopRightCam_textureView == null) return; - - mTopRightCam_PictureButton = findViewById(R.id.Picture1); - mTopRightCam_RecordButton = findViewById(R.id.Record1); - - mTopRightCam = new TopRightCam(MainActivity.this, mTopRightCam_textureView, - mTopRightCam_PictureButton, mTopRightCam_RecordButton); - } - - public void Open_BotmLeftCam() { - mBotmLeftCam_textureView = findViewById(R.id.textureview2); - if (mBotmLeftCam_textureView == null) return; - - mBotmLeftCam_PictureButton = findViewById(R.id.Picture2); - mBotmLeftCam_RecordButton = findViewById(R.id.Record2); - - mBotmLeftCam = new BotmLeftCam(MainActivity.this, mBotmLeftCam_textureView, - mBotmLeftCam_PictureButton, mBotmLeftCam_RecordButton); - } - - public void Open_BotmRightCam() { - mBotmRightCam_textureView = findViewById(R.id.textureview3); - if (mBotmRightCam_textureView == null) return; - - mBotmRightCam_PictureButton = findViewById(R.id.Picture3); - mBotmRightCam_RecordButton = findViewById(R.id.Record3); - - mBotmRightCam = new BotmRightCam(MainActivity.this, mBotmRightCam_textureView, - mBotmRightCam_PictureButton, mBotmRightCam_RecordButton); - } - - public boolean isVideoCaptureIntent() { - String action = this.getIntent().getAction(); - return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); - } - - public boolean isImageCaptureIntent() { - String action = this.getIntent().getAction(); - return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)); - } - - public void setVisibilityFrameLayout() { - frameView0 = findViewById(R.id.control1); - frameView1 = findViewById(R.id.control2); - frameView2 = findViewById(R.id.control3); - frameView3 = findViewById(R.id.control4); - - CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE); - try { - numOfCameras = manager.getCameraIdList().length; - // if (numCameras != "") { numOfCameras = Integer.parseInt(numCameras); } - Log.d(TAG, "onCreate Inside openCamera() Total Cameras: " + - manager.getCameraIdList().length); - - if (numOfCameras == 1) { - frameView1.setVisibility(FrameLayout.INVISIBLE); - frameView2.setVisibility(FrameLayout.INVISIBLE); - frameView3.setVisibility(FrameLayout.INVISIBLE); - Open_TopLeftCam(); - } else if (numOfCameras == 2) { - frameView2.setVisibility(FrameLayout.INVISIBLE); - frameView3.setVisibility(FrameLayout.INVISIBLE); - Open_TopLeftCam(); - Open_TopRightCam(); - } else if (numOfCameras == 3) { - frameView3.setVisibility(FrameLayout.INVISIBLE); - Open_TopLeftCam(); - Open_TopRightCam(); - Open_BotmLeftCam(); - } else if (numOfCameras == 4) { - Open_TopLeftCam(); - Open_TopRightCam(); - Open_BotmLeftCam(); - Open_BotmRightCam(); - } else { - Log.d(TAG, "No CAMERA CONNECTED"); - frameView0.setVisibility(FrameLayout.INVISIBLE); - frameView1.setVisibility(FrameLayout.INVISIBLE); - frameView2.setVisibility(FrameLayout.INVISIBLE); - frameView3.setVisibility(FrameLayout.INVISIBLE); - } - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == REQUEST_CAMERA_PERMISSION) { - if (grantResults[0] == PackageManager.PERMISSION_DENIED) { - // close the app - Toast.makeText(MainActivity.this, - "Sorry!!!, you can't use this app without granting permission", - Toast.LENGTH_LONG) - .show(); - finish(); - } - } - } - - private void manageTopLeftCam() { - if (mTopLeftCam == null) { - Open_TopLeftCam(); - frameView0.setVisibility(FrameLayout.VISIBLE); - } else if (mTopLeftCam_textureView == null) { - mTopLeftCam_textureView = findViewById(R.id.textureview0); - if (mTopLeftCam_textureView == null) return; - } - - if (mTopLeftCam_textureView.isAvailable()) { - frameView0.setVisibility(FrameLayout.VISIBLE); - 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, "onResume Total Cameras: " + manager.getCameraIdList().length); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - - if (numOfCameras == 1) { - manageTopLeftCam(); - } else if (numOfCameras == 2) { - 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); - frameView1.setVisibility(FrameLayout.INVISIBLE); - frameView2.setVisibility(FrameLayout.INVISIBLE); - frameView3.setVisibility(FrameLayout.INVISIBLE); - } - } - - private void closeCamera() { - if (null != mTopLeftCam) mTopLeftCam.closeCamera(); - - if (null != mTopRightCam) mTopRightCam.closeCamera(); - - if (null != mBotmRightCam) mBotmRightCam.closeCamera(); - - if (null != mBotmLeftCam) mBotmLeftCam.closeCamera(); - } - - @Override - protected void onPause() { - Log.e(TAG, "onPause"); - super.onPause(); - closeCamera(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - // noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - Intent intent = new Intent(MainActivity.this, SettingsActivity.class); - startActivity(intent); - return true; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/MultiViewActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/MultiViewActivity.java new file mode 100644 index 0000000..54df233 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/MultiViewActivity.java @@ -0,0 +1,835 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 2019 Intel Corporation. + * + * 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.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraManager; +import android.os.Bundle; +import android.util.Log; +import android.util.SparseIntArray; +import android.view.*; +import android.widget.*; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; + +public class MultiViewActivity extends AppCompatActivity { + private static final String TAG = "MainActivity"; + /** + * An {@link AutoFitTextureView} for camera preview. + */ + private AutoFitTextureView mTopLeftCam_textureView, mTopRightCam_textureView, + mBotmLeftCam_textureView, mBotmRightCam_textureView; + + private ImageButton mTopLeftCam_RecordButton, mTopLeftCam_PictureButton, + mTopRightCam_PictureButton, mBotmLeftCam_PictureButton, mBotmRightCam_PictureButton, + mTopRightCam_RecordButton, mBotmLeftCam_RecordButton, mBotmRightCam_RecordButton; + + private ImageButton SettingView0, SettingView1, SettingView2, SettingView3, SettingClose0, + SettingClose1, SettingClose2, SettingClose3, FullScrn0, FullScrn1, FullScrn2, FullScrn3, + exitScrn0, exitScrn1, exitScrn2, exitScrn3; + + private TextView mRecordingTimeView, mRecordingTimeView0, mRecordingTimeView1, + mRecordingTimeView2; + + private int numOfCameras; + private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); + private FrameLayout frameView0, frameView1, frameView2, frameView3; + + private CameraBase mTopRightCam, mBotmLeftCam, mBotmRightCam, mTopLeftCam; + + private SettingsPrefUtil Fragment, Fragment1, Fragment2, Fragment3; + + public String[] CameraIds; + private boolean mHasCriticalPermissions; + + static { + ORIENTATIONS.append(Surface.ROTATION_0, 90); + ORIENTATIONS.append(Surface.ROTATION_90, 0); + ORIENTATIONS.append(Surface.ROTATION_180, 270); + ORIENTATIONS.append(Surface.ROTATION_270, 180); + } + + private int[] FrameVisibility; + private boolean exitScrnFlag; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(true); + } + + setContentView(R.layout.activity_multiview); + + Log.e(TAG, "onCreate"); + + checkPermissions(); + if (!mHasCriticalPermissions) { + Log.v(TAG, "onCreate: Missing critical permissions."); + finish(); + return; + } + + Settings_Init(); + + FullScrn_Init(); + + setVisibilityFrameLayout(); + + set_FrameVisibilities(); + } + + private void set_FrameVisibilities() { + FrameVisibility = new int[4]; + + FrameVisibility[0] = frameView0.getVisibility(); + FrameVisibility[1] = frameView1.getVisibility(); + FrameVisibility[2] = frameView2.getVisibility(); + FrameVisibility[3] = frameView3.getVisibility(); + } + + /** + * 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(); + } + } + + private void FullScrn_Init() { + FullScrn0 = findViewById(R.id.imageView0); + FullScrn1 = findViewById(R.id.imageView1); + FullScrn2 = findViewById(R.id.imageView2); + FullScrn3 = findViewById(R.id.imageView3); + + exitScrn0 = findViewById(R.id.exitFullScreen0); + exitScrn1 = findViewById(R.id.exitFullScreen1); + exitScrn2 = findViewById(R.id.exitFullScreen2); + exitScrn3 = findViewById(R.id.exitFullScreen3); + } + + private void Settings_Init() { + SettingView0 = findViewById(R.id.SettingView0); + SettingView1 = findViewById(R.id.SettingView1); + SettingView2 = findViewById(R.id.SettingView2); + SettingView3 = findViewById(R.id.SettingView3); + + SettingClose0 = findViewById(R.id.mSettingClose0); + SettingClose1 = findViewById(R.id.mSettingClose1); + SettingClose2 = findViewById(R.id.mSettingClose2); + SettingClose3 = findViewById(R.id.mSettingClose3); + } + + public void GetCameraCnt() { + CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE); + + try { + CameraIds = manager.getCameraIdList(); + numOfCameras = manager.getCameraIdList().length; + Log.d(TAG, "Inside Settings Total Cameras: " + manager.getCameraIdList().length); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + public void Open_TopLeftCam() { + String[] Data = new String[5]; + ImageButton[] Buttons = new ImageButton[4]; + + mTopLeftCam_textureView = findViewById(R.id.textureview0); + if (mTopLeftCam_textureView == null) return; + + mTopLeftCam_PictureButton = findViewById(R.id.Picture0); + mTopLeftCam_RecordButton = findViewById(R.id.Record0); + + Buttons[0] = mTopLeftCam_PictureButton; + Buttons[1] = mTopLeftCam_RecordButton; + Buttons[2] = SettingView0; + Buttons[3] = FullScrn0; + + mRecordingTimeView = findViewById(R.id.recording_time); + + Data[0] = "TopLeftCam"; + Data[1] = CameraIds[0]; + Data[2] = "capture_list"; + Data[3] = "video_list"; + Data[4] = "pref_resolution"; + + RoundedThumbnailView roundedThumbnailView = findViewById(R.id.rounded_thumbnail_view); + + mTopLeftCam = new CameraBase(this, mTopLeftCam_textureView, Buttons, mRecordingTimeView, + Data, roundedThumbnailView); + } + + public void Open_TopRightCam() { + String[] Data = new String[5]; + ImageButton[] Buttons = new ImageButton[4]; + mTopRightCam_textureView = findViewById(R.id.textureview1); + if (mTopRightCam_textureView == null) return; + + mTopRightCam_PictureButton = findViewById(R.id.Picture1); + mTopRightCam_RecordButton = findViewById(R.id.Record1); + + Buttons[0] = mTopRightCam_PictureButton; + Buttons[1] = mTopRightCam_RecordButton; + Buttons[2] = SettingView1; + Buttons[3] = FullScrn1; + + Data[0] = "TopRightCam"; + Data[1] = CameraIds[1]; + Data[2] = "capture_list_1"; + Data[3] = "video_list_1"; + Data[4] = "pref_resolution_1"; + + mRecordingTimeView0 = findViewById(R.id.recording_time0); + + RoundedThumbnailView roundedThumbnailView = findViewById(R.id.rounded_thumbnail_view0); + + mTopRightCam = new CameraBase(this, mTopRightCam_textureView, Buttons, mRecordingTimeView0, + Data, roundedThumbnailView); + } + + public void Open_BotmLeftCam() { + String[] Data = new String[5]; + ImageButton[] Buttons = new ImageButton[4]; + mBotmLeftCam_textureView = findViewById(R.id.textureview2); + if (mBotmLeftCam_textureView == null) return; + + mBotmLeftCam_PictureButton = findViewById(R.id.Picture2); + mBotmLeftCam_RecordButton = findViewById(R.id.Record2); + + Buttons[0] = mBotmLeftCam_PictureButton; + Buttons[1] = mBotmLeftCam_RecordButton; + Buttons[2] = SettingView2; + Buttons[3] = FullScrn2; + + Data[0] = "BotmLeftCam"; + Data[1] = CameraIds[2]; + Data[2] = "capture_list_2"; + Data[3] = "video_list_2"; + Data[4] = "pref_resolution_2"; + + mRecordingTimeView1 = findViewById(R.id.recording_time1); + + RoundedThumbnailView roundedThumbnailView = findViewById(R.id.rounded_thumbnail_view1); + + mBotmLeftCam = new CameraBase(this, mBotmLeftCam_textureView, Buttons, mRecordingTimeView1, + Data, roundedThumbnailView); + } + + public void Open_BotmRightCam() { + String[] Data = new String[5]; + ImageButton[] Buttons = new ImageButton[4]; + mBotmRightCam_textureView = findViewById(R.id.textureview3); + if (mTopRightCam_textureView == null) return; + + mBotmRightCam_PictureButton = findViewById(R.id.Picture3); + mBotmRightCam_RecordButton = findViewById(R.id.Record3); + + Buttons[0] = mBotmRightCam_PictureButton; + Buttons[1] = mBotmRightCam_RecordButton; + Buttons[2] = SettingView3; + Buttons[3] = FullScrn3; + + Data[0] = "BotmRightCam"; + Data[1] = CameraIds[3]; + Data[2] = "capture_list_3"; + Data[3] = "video_list_3"; + Data[4] = "pref_resolution_3"; + + mRecordingTimeView2 = findViewById(R.id.recording_time2); + + RoundedThumbnailView roundedThumbnailView = findViewById(R.id.rounded_thumbnail_view2); + + mBotmRightCam = new CameraBase(this, mBotmRightCam_textureView, Buttons, + mRecordingTimeView2, Data, roundedThumbnailView); + } + + public void setVisibilityFrameLayout() { + frameView0 = findViewById(R.id.control1); + frameView1 = findViewById(R.id.control2); + frameView2 = findViewById(R.id.control3); + frameView3 = findViewById(R.id.control4); + + GetCameraCnt(); + Log.d(TAG, "onCreate Total Cameras: " + numOfCameras); + + if (numOfCameras == 1) { + frameView1.setVisibility(FrameLayout.INVISIBLE); + frameView2.setVisibility(FrameLayout.INVISIBLE); + frameView3.setVisibility(FrameLayout.INVISIBLE); + Open_TopLeftCam(); + } else if (numOfCameras == 2) { + frameView2.setVisibility(FrameLayout.INVISIBLE); + frameView3.setVisibility(FrameLayout.INVISIBLE); + Open_TopLeftCam(); + } else if (numOfCameras == 3) { + frameView3.setVisibility(FrameLayout.INVISIBLE); + Open_TopLeftCam(); + Open_TopRightCam(); + Open_BotmLeftCam(); + } else if (numOfCameras == 4) { + Open_TopLeftCam(); + Open_TopRightCam(); + Open_BotmLeftCam(); + Open_BotmRightCam(); + } else { + Log.d(TAG, "No CAMERA CONNECTED"); + frameView0.setVisibility(FrameLayout.INVISIBLE); + frameView1.setVisibility(FrameLayout.INVISIBLE); + frameView2.setVisibility(FrameLayout.INVISIBLE); + frameView3.setVisibility(FrameLayout.INVISIBLE); + } + } + + private void manageTopLeftCam() { + if (mTopLeftCam == null) { + Open_TopLeftCam(); + frameView0.setVisibility(FrameLayout.VISIBLE); + FrameVisibility[0] = frameView0.getVisibility(); + } else if (mTopLeftCam_textureView == null) { + mTopLeftCam_textureView = findViewById(R.id.textureview0); + } + + if (mTopLeftCam_textureView.isAvailable()) { + mTopLeftCam.textureListener.onSurfaceTextureAvailable( + mTopLeftCam_textureView.getSurfaceTexture(), mTopLeftCam_textureView.getWidth(), + mTopLeftCam_textureView.getHeight()); + } else { + mTopLeftCam_textureView.setSurfaceTextureListener(mTopLeftCam.textureListener); + } + } + + private void manageTopRightCam() { + if (mTopRightCam == null) { + Open_TopRightCam(); + frameView1.setVisibility(FrameLayout.VISIBLE); + FrameVisibility[1] = frameView1.getVisibility(); + + } else if (mTopRightCam_textureView == null) { + mTopRightCam_textureView = findViewById(R.id.textureview1); + } + + if (mTopRightCam_textureView.isAvailable()) { + mTopRightCam.textureListener.onSurfaceTextureAvailable( + mTopRightCam_textureView.getSurfaceTexture(), + mTopRightCam_textureView.getWidth(), mTopRightCam_textureView.getHeight()); + } else { + mTopRightCam_textureView.setSurfaceTextureListener(mTopRightCam.textureListener); + } + } + + private void manageBotmLeftCam() { + if (mBotmLeftCam == null) { + Open_BotmLeftCam(); + frameView2.setVisibility(FrameLayout.VISIBLE); + FrameVisibility[2] = frameView2.getVisibility(); + + } else if (mBotmLeftCam_textureView == null) { + mBotmLeftCam_textureView = findViewById(R.id.textureview2); + } + + if (mBotmLeftCam_textureView.isAvailable()) { + mBotmLeftCam.textureListener.onSurfaceTextureAvailable( + mBotmLeftCam_textureView.getSurfaceTexture(), + mBotmLeftCam_textureView.getWidth(), mBotmLeftCam_textureView.getHeight()); + } else { + mBotmLeftCam_textureView.setSurfaceTextureListener(mBotmLeftCam.textureListener); + } + } + + private void manageBotmRightCam() { + if (mBotmRightCam == null) { + Open_BotmRightCam(); + frameView3.setVisibility(FrameLayout.VISIBLE); + FrameVisibility[3] = frameView3.getVisibility(); + + } else if (mBotmRightCam_textureView == null) { + mBotmRightCam_textureView = findViewById(R.id.textureview3); + } + + if (mBotmRightCam_textureView.isAvailable()) { + mBotmRightCam.textureListener.onSurfaceTextureAvailable( + mBotmRightCam_textureView.getSurfaceTexture(), + mBotmRightCam_textureView.getWidth(), mBotmRightCam_textureView.getHeight()); + } else { + mBotmRightCam_textureView.setSurfaceTextureListener(mBotmRightCam.textureListener); + } + } + + @Override + protected void onResume() { + super.onResume(); + Log.e(TAG, "onResume"); + + GetCameraCnt(); + + if (numOfCameras == 1) { + manageTopLeftCam(); + } else if (numOfCameras == 2) { + 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); + frameView1.setVisibility(FrameLayout.INVISIBLE); + frameView2.setVisibility(FrameLayout.INVISIBLE); + frameView3.setVisibility(FrameLayout.INVISIBLE); + + FrameVisibility[0] = frameView0.getVisibility(); + FrameVisibility[1] = frameView1.getVisibility(); + FrameVisibility[2] = frameView2.getVisibility(); + FrameVisibility[3] = frameView3.getVisibility(); + } + } + + private void closeCamera() { + if (null != mTopLeftCam) mTopLeftCam.closeCamera(); + + if (null != mTopRightCam) mTopRightCam.closeCamera(); + + if (null != mBotmRightCam) mBotmRightCam.closeCamera(); + + if (null != mBotmLeftCam) mBotmLeftCam.closeCamera(); + } + + @Override + protected void onPause() { + Log.e(TAG, "onPause"); + super.onPause(); + + closeCamera(); + } + + public void settingView(View view) { + FrameLayout frameLayout; + Bundle bundle; + + switch (view.getId()) { + case R.id.SettingView0: + frameLayout = findViewById(R.id.PrefScrnSettings0); + + frameLayout.setVisibility(View.VISIBLE); + SettingClose0.setVisibility(View.VISIBLE); + + bundle = new Bundle(); + bundle.putString("Camera_id", CameraIds[0]); + bundle.putInt("root_preferences", R.xml.root_preferences); + bundle.putString("pref_resolution", "pref_resolution"); + bundle.putString("video_list", "video_list"); + bundle.putString("capture_list", "capture_list"); + + Fragment = new SettingsPrefUtil(); + Fragment.setArguments(bundle); + + getFragmentManager() + .beginTransaction() + .replace(R.id.PrefScrnSettings0, Fragment) + .commit(); + + SettingView0.setVisibility(View.GONE); + FullScrn0.setVisibility(View.GONE); + if (exitScrnFlag) exitScrn0.setVisibility(View.INVISIBLE); + mTopLeftCam_RecordButton.setVisibility(View.GONE); + mTopLeftCam_PictureButton.setVisibility(View.GONE); + break; + case R.id.SettingView1: + frameLayout = findViewById(R.id.PrefScrnSettings1); + + frameLayout.setVisibility(View.VISIBLE); + SettingClose1.setVisibility(View.VISIBLE); + + bundle = new Bundle(); + bundle.putString("Camera_id", CameraIds[1]); + bundle.putInt("root_preferences", R.xml.root_preferences_1); + bundle.putString("pref_resolution", "pref_resolution_1"); + bundle.putString("video_list", "video_list_1"); + bundle.putString("capture_list", "capture_list_1"); + + Fragment1 = new SettingsPrefUtil(); + Fragment1.setArguments(bundle); + + getFragmentManager() + .beginTransaction() + .replace(R.id.PrefScrnSettings1, Fragment1) + .commit(); + + SettingView1.setVisibility(View.GONE); + FullScrn1.setVisibility(View.GONE); + if (exitScrnFlag) exitScrn1.setVisibility(View.INVISIBLE); + mTopRightCam_RecordButton.setVisibility(View.GONE); + mTopRightCam_PictureButton.setVisibility(View.GONE); + break; + case R.id.SettingView2: + frameLayout = findViewById(R.id.PrefScrnSettings2); + + frameLayout.setVisibility(View.VISIBLE); + SettingClose2.setVisibility(View.VISIBLE); + + bundle = new Bundle(); + bundle.putString("Camera_id", CameraIds[2]); + bundle.putInt("root_preferences", R.xml.root_preferences_2); + bundle.putString("pref_resolution", "pref_resolution_2"); + bundle.putString("video_list", "video_list_2"); + bundle.putString("capture_list", "capture_list_2"); + + Fragment2 = new SettingsPrefUtil(); + Fragment2.setArguments(bundle); + + getFragmentManager() + .beginTransaction() + .replace(R.id.PrefScrnSettings2, Fragment2) + .commit(); + + SettingView2.setVisibility(View.GONE); + FullScrn2.setVisibility(View.GONE); + if (exitScrnFlag) exitScrn2.setVisibility(View.INVISIBLE); + mBotmLeftCam_RecordButton.setVisibility(View.GONE); + mBotmLeftCam_PictureButton.setVisibility(View.GONE); + break; + case R.id.SettingView3: + frameLayout = findViewById(R.id.PrefScrnSettings3); + + frameLayout.setVisibility(View.VISIBLE); + SettingClose3.setVisibility(View.VISIBLE); + + bundle = new Bundle(); + bundle.putString("Camera_id", CameraIds[3]); + bundle.putInt("root_preferences", R.xml.root_preferences_3); + bundle.putString("pref_resolution", "pref_resolution_3"); + bundle.putString("video_list", "video_list_3"); + bundle.putString("capture_list", "capture_list_3"); + + Fragment3 = new SettingsPrefUtil(); + Fragment3.setArguments(bundle); + + getFragmentManager() + .beginTransaction() + .replace(R.id.PrefScrnSettings3, Fragment3) + .commit(); + + SettingView3.setVisibility(View.GONE); + FullScrn3.setVisibility(View.GONE); + if (exitScrnFlag) exitScrn3.setVisibility(View.INVISIBLE); + mBotmRightCam_RecordButton.setVisibility(View.GONE); + mBotmRightCam_PictureButton.setVisibility(View.GONE); + break; + + default: + break; + } + } + + public void settingClose(View view) { + FrameLayout frameLayout; + + switch (view.getId()) { + case R.id.mSettingClose0: + frameLayout = findViewById(R.id.PrefScrnSettings0); + + getFragmentManager().beginTransaction().remove(Fragment).commit(); + + frameLayout.setVisibility(View.GONE); + view.setVisibility(view.GONE); + SettingView0.setVisibility(View.VISIBLE); + + if (!exitScrnFlag) + FullScrn0.setVisibility(View.VISIBLE); + else + exitScrn0.setVisibility(View.VISIBLE); + + mTopLeftCam_RecordButton.setVisibility(View.VISIBLE); + mTopLeftCam_PictureButton.setVisibility(View.VISIBLE); + + mTopLeftCam.createCameraPreview(); + + break; + case R.id.mSettingClose1: + frameLayout = findViewById(R.id.PrefScrnSettings1); + + getFragmentManager().beginTransaction().remove(Fragment1).commit(); + + frameLayout.setVisibility(View.GONE); + view.setVisibility(view.GONE); + SettingView1.setVisibility(View.VISIBLE); + + if (!exitScrnFlag) + FullScrn1.setVisibility(View.VISIBLE); + else + exitScrn1.setVisibility(View.VISIBLE); + + mTopRightCam_RecordButton.setVisibility(View.VISIBLE); + mTopRightCam_PictureButton.setVisibility(View.VISIBLE); + + mTopRightCam.createCameraPreview(); + + break; + case R.id.mSettingClose2: + frameLayout = findViewById(R.id.PrefScrnSettings2); + getFragmentManager().beginTransaction().remove(Fragment2).commit(); + + frameLayout.setVisibility(View.GONE); + view.setVisibility(view.GONE); + SettingView2.setVisibility(View.VISIBLE); + + if (!exitScrnFlag) + FullScrn2.setVisibility(View.VISIBLE); + else + exitScrn2.setVisibility(View.VISIBLE); + + mBotmLeftCam_RecordButton.setVisibility(View.VISIBLE); + mBotmLeftCam_PictureButton.setVisibility(View.VISIBLE); + + mBotmLeftCam.createCameraPreview(); + + break; + case R.id.mSettingClose3: + frameLayout = findViewById(R.id.PrefScrnSettings3); + getFragmentManager().beginTransaction().remove(Fragment3).commit(); + + frameLayout.setVisibility(View.GONE); + view.setVisibility(view.GONE); + SettingView3.setVisibility(View.VISIBLE); + + if (!exitScrnFlag) + FullScrn3.setVisibility(View.VISIBLE); + else + exitScrn3.setVisibility(View.VISIBLE); + + mBotmRightCam_RecordButton.setVisibility(View.VISIBLE); + mBotmRightCam_PictureButton.setVisibility(View.VISIBLE); + + mBotmRightCam.createCameraPreview(); + + break; + + default: + break; + } + } + + public void enterFullScreen(View view) { + LinearLayout LinLayout1, LinLayout2; + + LinLayout1 = findViewById(R.id.TopLayout); + + LinLayout2 = findViewById(R.id.BtmLayout); + + switch (view.getId()) { + case R.id.imageView0: + this.setTitle("TopLeftCam"); + exitScrnFlag = true; + exitScrn0.setVisibility(View.VISIBLE); + FullScrn0.setVisibility(View.GONE); + + frameView1.setVisibility(View.GONE); + + LinLayout2.setVisibility(View.GONE); + + break; + case R.id.exitFullScreen0: + this.setTitle("MultiCamera"); + exitScrnFlag = false; + + FullScrn0.setVisibility(View.VISIBLE); + + exitScrn0.setVisibility(View.GONE); + + switch (FrameVisibility[1]) { + case View.VISIBLE: + + frameView1.setVisibility(View.VISIBLE); + + break; + + case View.INVISIBLE: + + frameView1.setVisibility(View.INVISIBLE); + + break; + default: + break; + } + + FrameVisibility[1] = frameView1.getVisibility(); + + LinLayout2.setVisibility(View.VISIBLE); + + break; + case R.id.imageView1: + this.setTitle("TopRightCam"); + exitScrnFlag = true; + FullScrn1.setVisibility(View.GONE); + exitScrn1.setVisibility(View.VISIBLE); + + frameView0.setVisibility(View.GONE); + LinLayout2.setVisibility(View.GONE); + + break; + case R.id.exitFullScreen1: + this.setTitle("MultiCamera"); + + exitScrnFlag = false; + FullScrn1.setVisibility(View.VISIBLE); + exitScrn1.setVisibility(View.GONE); + + switch (FrameVisibility[0]) { + case View.VISIBLE: + + frameView0.setVisibility(View.VISIBLE); + + break; + + case View.INVISIBLE: + + frameView0.setVisibility(View.INVISIBLE); + + break; + default: + break; + } + + FrameVisibility[0] = frameView0.getVisibility(); + + LinLayout2.setVisibility(View.VISIBLE); + + break; + + case R.id.imageView2: + this.setTitle("BotmLeftCam"); + + exitScrnFlag = true; + FullScrn2.setVisibility(View.GONE); + exitScrn2.setVisibility(View.VISIBLE); + + frameView3.setVisibility(View.GONE); + LinLayout1.setVisibility(View.GONE); + + break; + case R.id.exitFullScreen2: + this.setTitle("MultiCamera"); + + exitScrnFlag = false; + FullScrn2.setVisibility(View.VISIBLE); + exitScrn2.setVisibility(View.GONE); + + switch (FrameVisibility[3]) { + case View.VISIBLE: + + frameView3.setVisibility(View.VISIBLE); + + break; + + case View.INVISIBLE: + + frameView3.setVisibility(View.INVISIBLE); + + break; + default: + break; + } + + FrameVisibility[3] = frameView3.getVisibility(); + + LinLayout1.setVisibility(View.VISIBLE); + + break; + case R.id.imageView3: + this.setTitle("BotmRightCam"); + exitScrnFlag = true; + FullScrn3.setVisibility(View.GONE); + exitScrn3.setVisibility(View.VISIBLE); + + frameView2.setVisibility(View.GONE); + LinLayout1.setVisibility(View.GONE); + + break; + case R.id.exitFullScreen3: + this.setTitle("MultiCamera"); + exitScrnFlag = false; + FullScrn3.setVisibility(View.VISIBLE); + exitScrn3.setVisibility(View.GONE); + + switch (FrameVisibility[2]) { + case View.VISIBLE: + + frameView2.setVisibility(View.VISIBLE); + + break; + + case View.INVISIBLE: + + frameView2.setVisibility(View.INVISIBLE); + + break; + default: + break; + } + + FrameVisibility[2] = frameView2.getVisibility(); + + LinLayout1.setVisibility(View.VISIBLE); + + break; + default: + break; + } + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java index d790051..8cce815 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java @@ -37,44 +37,23 @@ 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); } @@ -89,7 +68,6 @@ protected void onResumeTasks() { protected void onDestroyTasks() { Log.v(TAG, "onDestroy: unregistering receivers"); - // unregisterReceiver(mShutdownReceiver); } /** @@ -104,10 +82,8 @@ public boolean convertToBoolean(String value) { if (value.compareTo("false") == 0) { ret = false; - Log.d(TAG, "####FALSE"); } else if (value.compareTo("true") == 0) { - Log.d(TAG, "####TRUE"); ret = true; } @@ -123,7 +99,7 @@ public String getString(String key, String defaultValue) { try { return preferences.getString(key, defaultValue); } catch (ClassCastException e) { - Log.w(TAG, "existing preference with invalid type, removing and returning default", e); + Log.w(TAG, "existing preference with invalid type,removing and returning default", e); preferences.edit().remove(key).apply(); return defaultValue; } @@ -149,7 +125,7 @@ private void checkPermissions() { } if (ActivityCompat.checkSelfPermission(getApplicationContext(), - Manifest.permission.READ_EXTERNAL_STORAGE) != + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { mNumPermissionsToRequest++; mShouldRequestStoragePermission = true; @@ -160,11 +136,13 @@ private void checkPermissions() { 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(); } @@ -238,7 +216,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], } private void handlePermissionsSuccess() { - Intent intent = new Intent(this, MainActivity.class); + Intent intent = new Intent(this, MultiViewActivity.class); startActivity(intent); finish(); } diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java index b839edb..a2b99da 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java @@ -53,12 +53,18 @@ public abstract class QuickActivity extends Activity { private String TAG = "QuickActivity"; - /** onResume tasks delay from secure lockscreen. */ + /** + * onResume tasks delay from secure lockscreen. + */ private static final long ON_RESUME_DELAY_SECURE_MILLIS = 30; - /** onResume tasks delay from non-secure lockscreen. */ + /** + * 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. */ + /** + * A reference to the main handler on which to run lifecycle methods. + */ private Handler mMainHandler; /** @@ -67,9 +73,13 @@ public abstract class QuickActivity extends Activity { */ private boolean mSkippedFirstOnResume = false; - /** When application execution started in SystemClock.elapsedRealtimeNanos(). */ + /** + * When application execution started in SystemClock.elapsedRealtimeNanos(). + */ protected long mExecutionStartNanoTime = 0; - /** Was this session started with onCreate(). */ + /** + * Was this session started with onCreate(). + */ protected boolean mStartupOnCreate = false; /** diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/RoundedThumbnailView.java b/camera/MultiCameraApplication/java/com/intel/multicamera/RoundedThumbnailView.java new file mode 100644 index 0000000..41398d2 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/RoundedThumbnailView.java @@ -0,0 +1,768 @@ +/* + * 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.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import java.util.Optional; + +/** + * A view that shows a pop-out effect for a thumbnail image as the new capture indicator design for + * Haleakala. When a photo is taken, this view will appear in the bottom right corner of the view + * finder to indicate the capture is done. + *

+ * Thumbnail cropping: + * (1) 100% width and vertically centered for portrait. + * (2) 100% height and horizontally centered for landscape. + *

+ * General behavior spec: Hide the capture indicator by fading out using fast_out_linear_in (150ms): + * (1) User open filmstrip. + * (2) User switch module. + * (3) User switch front/back camera. + * (4) User close app. + *

+ * Visual spec: + * (1) A 12dp spacing between mode option overlay and thumbnail. + * (2) A circular mask that excludes the corners of the preview image. + * (3) A solid white layer that sits on top of the preview and is also masked by 2). + * (4) The preview thumbnail image. + * (5) A 'ripple' which is just a white circular stroke. + *

+ * Animation spec: + * - For (2) only the scale animates, from 50%(24dp) to 114%(54dp) in 200ms then falls back to + * 100%(48dp) in 200ms. Both steps use the same easing: fast_out_slow_in. + * - For (3), change opacity from 50% to 0% over 150ms, easing is exponential. + * - For (4), doesn't animate. + * - For (5), starts animating after 100ms, when (1) is at its peak radius and all animations take + * 200ms, using linear_out_slow in. Opacity goes from 40% to 0%, radius goes from 40dp to 70dp, + * stroke width goes from 5dp to 1dp. + */ +public class RoundedThumbnailView extends View { + private static final String TAG = "RoundedThumbnailView"; + + // Configurations for the thumbnail pop-out effect. + private static final long THUMBNAIL_STRETCH_DURATION_MS = 200; + private static final long THUMBNAIL_SHRINK_DURATION_MS = 200; + private static final float THUMBNAIL_REVEAL_CIRCLE_OPACITY_BEGIN = 0.5f; + private static final float THUMBNAIL_REVEAL_CIRCLE_OPACITY_END = 0.0f; + + // Configurations for the ripple effect. + private static final long RIPPLE_DURATION_MS = 200; + private static final float RIPPLE_OPACITY_BEGIN = 0.4f; + private static final float RIPPLE_OPACITY_END = 0.0f; + + // Configurations for the hit-state effect. + private static final float HIT_STATE_CIRCLE_OPACITY_HIDDEN = -1.0f; + private static final float HIT_STATE_CIRCLE_OPACITY_BEGIN = 0.7f; + private static final float HIT_STATE_CIRCLE_OPACITY_END = 0.0f; + private static final long HIT_STATE_DURATION_MS = 150; + + /** + * Defines call events. + */ + public interface Callback { public void onHitStateFinished(); } + + /** + * The registered callback. + */ + private Optional mCallback; + + // Fields for view layout. + private float mThumbnailPadding; + private RectF mViewRect; + + // Fields for the thumbnail pop-out effect. + /** + * The animators to move the thumbnail. + */ + private AnimatorSet mThumbnailAnimatorSet; + /** + * The current diameter for the thumbnail image. + */ + private float mCurrentThumbnailDiameter; + /** + * The current reveal circle opacity. + */ + private float mCurrentRevealCircleOpacity; + /** + * The duration of the stretch phase in thumbnail pop-out effect. + */ + private long mThumbnailStretchDurationMs; + /** + * The duration of the shrink phase in thumbnail pop-out effect. + */ + private long mThumbnailShrinkDurationMs; + /** + * The beginning diameter of the thumbnail for the stretch phase in + * thumbnail pop-out effect. + */ + private float mThumbnailStretchDiameterBegin; + /** + * The ending diameter of the thumbnail for the stretch phase in thumbnail + * pop-out effect. + */ + private float mThumbnailStretchDiameterEnd; + /** + * The beginning diameter of the thumbnail for the shrink phase in thumbnail + * pop-out effect. + */ + private float mThumbnailShrinkDiameterBegin; + /** + * The ending diameter of the thumbnail for the shrink phase in thumbnail + * pop-out effect. + */ + private float mThumbnailShrinkDiameterEnd; + /** + * Paint object for the reveal circle. + */ + private final Paint mRevealCirclePaint; + + // Fields for the ripple effect. + /** + * The start delay of the ripple effect. + */ + private long mRippleStartDelayMs; + /** + * The duration of the ripple effect. + */ + private long mRippleDurationMs; + /** + * The beginning diameter of the ripple ring. + */ + private float mRippleRingDiameterBegin; + /** + * The ending diameter of the ripple ring. + */ + private float mRippleRingDiameterEnd; + /** + * The beginning thickness of the ripple ring. + */ + private float mRippleRingThicknessBegin; + /** + * The ending thickness of the ripple ring. + */ + private float mRippleRingThicknessEnd; + /** + * A lazily loaded animator for the ripple effect. + */ + private ValueAnimator mRippleAnimator; + /** + * The current ripple ring diameter which is updated by the ripple animator + * and used by onDraw(). + */ + private float mCurrentRippleRingDiameter; + /** + * The current ripple ring thickness which is updated by the ripple animator + * and used by onDraw(). + */ + private float mCurrentRippleRingThickness; + /** + * The current ripple ring opacity which is updated by the ripple animator + * and used by onDraw(). + */ + private float mCurrentRippleRingOpacity; + /** + * The paint used for drawing the ripple effect. + */ + private final Paint mRipplePaint; + + // Fields for the hit state effect. + /** + * The paint to draw hit state circle. + */ + private final Paint mHitStateCirclePaint; + /** + * The current hit state circle opacity (0.0 - 1.0) which is updated by the + * hit state animator. If -1, the hit state circle won't be drawn. + */ + private float mCurrentHitStateCircleOpacity; + + /** + * The pending reveal request. This is created when start is called, but is + * not drawn until the thumbnail is available. Once the bitmap is available + * it is swapped into the foreground request. + */ + private RevealRequest mPendingRequest; + + /** + * The currently animating reveal request. + */ + private RevealRequest mForegroundRequest; + + /** + * The latest finished reveal request. Its thumbnail will be shown until + * a newer one replace it. + */ + private RevealRequest mBackgroundRequest; + + private View.OnClickListener mOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + // Trigger the hit state animation. Fade out the hit state white + // circle by changing the alpha. + final ValueAnimator hitStateAnimator = ValueAnimator.ofFloat( + HIT_STATE_CIRCLE_OPACITY_BEGIN, HIT_STATE_CIRCLE_OPACITY_END); + hitStateAnimator.setDuration(HIT_STATE_DURATION_MS); + hitStateAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + hitStateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mCurrentHitStateCircleOpacity = (Float)valueAnimator.getAnimatedValue(); + invalidate(); + } + }); + hitStateAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + + mCurrentHitStateCircleOpacity = HIT_STATE_CIRCLE_OPACITY_HIDDEN; + + if (mCallback.isPresent()) { + mCallback.get().onHitStateFinished(); + } + } + }); + hitStateAnimator.start(); + } + }; + + /** + * Constructs a RoundedThumbnailView. + */ + public RoundedThumbnailView(Context context, AttributeSet attrs) { + super(context, attrs); + + mCallback = Optional.empty(); + + // Make the view clickable. + setClickable(true); + setOnClickListener(mOnClickListener); + + mThumbnailPadding = getResources().getDimension(R.dimen.rounded_thumbnail_padding); + + // Load thumbnail pop-out effect constants. + mThumbnailStretchDurationMs = THUMBNAIL_STRETCH_DURATION_MS; + mThumbnailShrinkDurationMs = THUMBNAIL_SHRINK_DURATION_MS; + mThumbnailStretchDiameterBegin = + getResources().getDimension(R.dimen.rounded_thumbnail_diameter_min); + mThumbnailStretchDiameterEnd = + getResources().getDimension(R.dimen.rounded_thumbnail_diameter_max); + mThumbnailShrinkDiameterBegin = mThumbnailStretchDiameterEnd; + mThumbnailShrinkDiameterEnd = + getResources().getDimension(R.dimen.rounded_thumbnail_diameter_normal); + // Load ripple effect constants. + float startDelayRatio = 0.5f; + mRippleStartDelayMs = (long)(mThumbnailStretchDurationMs * startDelayRatio); + mRippleDurationMs = RIPPLE_DURATION_MS; + mRippleRingDiameterEnd = + getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_diameter_max); + + mViewRect = new RectF(0, 0, mRippleRingDiameterEnd, mRippleRingDiameterEnd); + + mRippleRingDiameterBegin = + getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_diameter_min); + mRippleRingThicknessBegin = + getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_thick_max); + mRippleRingThicknessEnd = + getResources().getDimension(R.dimen.rounded_thumbnail_ripple_ring_thick_min); + + mCurrentHitStateCircleOpacity = HIT_STATE_CIRCLE_OPACITY_HIDDEN; + // Draw the reveal while circle. + mHitStateCirclePaint = new Paint(); + mHitStateCirclePaint.setAntiAlias(true); + mHitStateCirclePaint.setColor(Color.WHITE); + mHitStateCirclePaint.setStyle(Paint.Style.FILL); + + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + mRipplePaint.setColor(Color.WHITE); + mRipplePaint.setStyle(Paint.Style.STROKE); + + mRevealCirclePaint = new Paint(); + mRevealCirclePaint.setAntiAlias(true); + mRevealCirclePaint.setColor(Color.WHITE); + mRevealCirclePaint.setStyle(Paint.Style.FILL); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Ignore the spec since the size should be fixed. + int desiredSize = (int)mRippleRingDiameterEnd; + setMeasuredDimension(desiredSize, desiredSize); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + final float centerX = canvas.getWidth() / 2; + final float centerY = canvas.getHeight() / 2; + + final float viewDiameter = mRippleRingDiameterEnd; + final float finalDiameter = mThumbnailShrinkDiameterEnd; + + canvas.clipRect(mViewRect); + + // Draw the thumbnail of latest finished reveal request. + if (mBackgroundRequest != null) { + Paint thumbnailPaint = mBackgroundRequest.getThumbnailPaint(); + if (thumbnailPaint != null) { + // Draw the old thumbnail with the final diameter. + float scaleRatio = finalDiameter / viewDiameter; + + canvas.save(); + canvas.scale(scaleRatio, scaleRatio, centerX, centerY); + canvas.drawRoundRect(mViewRect, centerX, centerY, thumbnailPaint); + canvas.restore(); + } + } + + // Draw animated parts (thumbnail and ripple) if there exists a reveal request. + if (mForegroundRequest != null) { + // Draw ripple ring first or the ring will cover thumbnail. + if (mCurrentRippleRingThickness > 0) { + // Draw the ripple ring. + mRipplePaint.setAlpha((int)(mCurrentRippleRingOpacity * 255)); + mRipplePaint.setStrokeWidth(mCurrentRippleRingThickness); + + canvas.save(); + canvas.drawCircle(centerX, centerY, mCurrentRippleRingDiameter / 2, mRipplePaint); + canvas.restore(); + } + + // Achieve the animation effect by scaling the transformation matrix. + float scaleRatio = mCurrentThumbnailDiameter / mRippleRingDiameterEnd; + + canvas.save(); + canvas.scale(scaleRatio, scaleRatio, centerX, centerY); + + // Draw the new popping up thumbnail. + Paint thumbnailPaint = mForegroundRequest.getThumbnailPaint(); + if (thumbnailPaint != null) { + canvas.drawRoundRect(mViewRect, centerX, centerY, thumbnailPaint); + } + + // Draw the reveal while circle. + mRevealCirclePaint.setAlpha((int)(mCurrentRevealCircleOpacity * 255)); + canvas.drawCircle(centerX, centerY, mRippleRingDiameterEnd / 2, mRevealCirclePaint); + + canvas.restore(); + } + + // Draw hit state circle if necessary. + if (mCurrentHitStateCircleOpacity != HIT_STATE_CIRCLE_OPACITY_HIDDEN) { + canvas.save(); + final float scaleRatio = finalDiameter / viewDiameter; + canvas.scale(scaleRatio, scaleRatio, centerX, centerY); + + // Draw the hit state while circle. + mHitStateCirclePaint.setAlpha((int)(mCurrentHitStateCircleOpacity * 255)); + canvas.drawCircle(centerX, centerY, mRippleRingDiameterEnd / 2, mHitStateCirclePaint); + canvas.restore(); + } + } + + /** + * Sets the callback. + * + * @param callback The callback to be set. + */ + public void setCallback(Callback callback) { + mCallback = Optional.of(callback); + } + + /** + * Gets the padding size with mode options and preview edges. + * + * @return The padding size with mode options and preview edges. + */ + public float getThumbnailPadding() { + return mThumbnailPadding; + } + + /** + * Gets the diameter of the thumbnail image after the revealing animation. + * + * @return The diameter of the thumbnail image after the revealing animation. + */ + public float getThumbnailFinalDiameter() { + return mThumbnailShrinkDiameterEnd; + } + + /** + * Starts the thumbnail revealing animation. + * + * @param accessibilityString An accessibility String to be announced during the revealing + * animation. + */ + public void startRevealThumbnailAnimation(String accessibilityString) { + // MainThread.checkMainThread(); + // Create a new request. + mPendingRequest = new RevealRequest(getMeasuredWidth(), accessibilityString); + } + + /** + * Updates the thumbnail image. + * + * @param thumbnailBitmap The thumbnail image to be shown. + * @param rotation The orientation of the image in degrees. + */ + public void setThumbnail(final Bitmap thumbnailBitmap, final int rotation) { + // MainThread.checkMainThread(); + + if (mPendingRequest != null) { + mPendingRequest.setThumbnailBitmap(thumbnailBitmap, rotation); + + runPendingRequestAnimation(); + } else { + Log.e(TAG, "Pending thumb was null!"); + } + } + + /** + * Hide the thumbnail. + */ + public void hideThumbnail() { + + setVisibility(GONE); + + clearAnimations(); + + // Remove all pending reveal requests. + mPendingRequest = null; + mForegroundRequest = null; + mBackgroundRequest = null; + } + + /** + * Stop currently running animators. + */ + private void clearAnimations() { + // Stop currently running animators. + if (mThumbnailAnimatorSet != null && mThumbnailAnimatorSet.isRunning()) { + mThumbnailAnimatorSet.removeAllListeners(); + mThumbnailAnimatorSet.cancel(); + // Release the animator so that a new instance will be created and + // its listeners properly reconnected. Fix for b/19034435 + mThumbnailAnimatorSet = null; + } + + if (mRippleAnimator != null && mRippleAnimator.isRunning()) { + mRippleAnimator.removeAllListeners(); + mRippleAnimator.cancel(); + // Release the animator so that a new instance will be created and + // its listeners properly reconnected. Fix for b/19034435 + mRippleAnimator = null; + } + } + + public void Invisible() { + setVisibility(INVISIBLE); + } + + /** + * Set the foreground request to the background, complete it, and run the + * animation for the pending thumbnail. + */ + private void runPendingRequestAnimation() { + // Shift foreground to background, and pending to foreground. + if (mForegroundRequest != null) { + mBackgroundRequest = mForegroundRequest; + mBackgroundRequest.finishRippleAnimation(); + mBackgroundRequest.finishThumbnailAnimation(); + } + + mForegroundRequest = mPendingRequest; + mPendingRequest = null; + + // Make this view visible. + + setVisibility(VISIBLE); + + // Ensure there are no running animations. + clearAnimations(); + + Interpolator stretchInterpolator; + stretchInterpolator = new AccelerateDecelerateInterpolator(); + + // The first phase of thumbnail animation. Stretch the thumbnail to the maximal size. + ValueAnimator stretchAnimator = + ValueAnimator.ofFloat(mThumbnailStretchDiameterBegin, mThumbnailStretchDiameterEnd); + stretchAnimator.setDuration(mThumbnailStretchDurationMs); + stretchAnimator.setInterpolator(stretchInterpolator); + stretchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mCurrentThumbnailDiameter = (Float)valueAnimator.getAnimatedValue(); + float fraction = valueAnimator.getAnimatedFraction(); + float opacityDiff = + THUMBNAIL_REVEAL_CIRCLE_OPACITY_END - THUMBNAIL_REVEAL_CIRCLE_OPACITY_BEGIN; + mCurrentRevealCircleOpacity = + THUMBNAIL_REVEAL_CIRCLE_OPACITY_BEGIN + fraction * opacityDiff; + invalidate(); + } + }); + + // The second phase of thumbnail animation. Shrink the thumbnail to the final size. + Interpolator shrinkInterpolator = stretchInterpolator; + ValueAnimator shrinkAnimator = + ValueAnimator.ofFloat(mThumbnailShrinkDiameterBegin, mThumbnailShrinkDiameterEnd); + shrinkAnimator.setDuration(mThumbnailShrinkDurationMs); + shrinkAnimator.setInterpolator(shrinkInterpolator); + shrinkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mCurrentThumbnailDiameter = (Float)valueAnimator.getAnimatedValue(); + invalidate(); + } + }); + + // The stretch and shrink animators play sequentially. + mThumbnailAnimatorSet = new AnimatorSet(); + mThumbnailAnimatorSet.playSequentially(stretchAnimator, shrinkAnimator); + mThumbnailAnimatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mForegroundRequest != null) { + // Mark the thumbnail animation as finished. + mForegroundRequest.finishThumbnailAnimation(); + processRevealRequests(); + } + } + }); + + // Start thumbnail animation immediately. + mThumbnailAnimatorSet.start(); + + // When start shrinking the thumbnail, a ripple effect is triggered at the same time. + mRippleAnimator = ValueAnimator.ofFloat(mRippleRingDiameterBegin, mRippleRingDiameterEnd); + mRippleAnimator.setDuration(mRippleDurationMs); + mRippleAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mForegroundRequest != null) { + mForegroundRequest.finishRippleAnimation(); + processRevealRequests(); + } + } + }); + mRippleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mCurrentRippleRingDiameter = (Float)valueAnimator.getAnimatedValue(); + float fraction = valueAnimator.getAnimatedFraction(); + mCurrentRippleRingThickness = + mRippleRingThicknessBegin + + fraction * (mRippleRingThicknessEnd - mRippleRingThicknessBegin); + mCurrentRippleRingOpacity = RIPPLE_OPACITY_BEGIN + + fraction * (RIPPLE_OPACITY_END - RIPPLE_OPACITY_BEGIN); + invalidate(); + } + }); + + // Start ripple animation after delay. + mRippleAnimator.setStartDelay(mRippleStartDelayMs); + mRippleAnimator.start(); + + // Announce the accessibility string. + // announceForAccessibility(mForegroundRequest.getAccessibilityString()); + } + + private void processRevealRequests() { + if (mForegroundRequest != null && mForegroundRequest.isFinished()) { + mBackgroundRequest = mForegroundRequest; + mForegroundRequest = null; + } + } + + @Override + public boolean hasOverlappingRendering() { + return true; + } + + /** + * Encapsulates necessary information for a complete thumbnail reveal animation. + */ + private static class RevealRequest { + // The size of the thumbnail. + private float mViewSize; + + // The accessibility string. + private String mAccessibilityString; + + // The cached Paint object to draw the thumbnail. + private Paint mThumbnailPaint; + + // The flag to indicate if thumbnail animation of this request is full-filled. + private boolean mThumbnailAnimationFinished; + + // The flag to indicate if ripple animation of this request is full-filled. + private boolean mRippleAnimationFinished; + + /** + * Constructs a reveal request. Use setThumbnailBitmap() to specify a source bitmap for the + * thumbnail. + * + * @param viewSize The size of the capture indicator view. + * @param accessibilityString The accessibility string of the request. + */ + public RevealRequest(float viewSize, String accessibilityString) { + mAccessibilityString = accessibilityString; + mViewSize = viewSize; + } + + /** + * Returns the accessibility string. + * + * @return the accessibility string. + */ + public String getAccessibilityString() { + return mAccessibilityString; + } + + /** + * Returns the paint object which can be used to draw the thumbnail on a Canvas. + * + * @return the paint object which can be used to draw the thumbnail on a Canvas. + */ + public Paint getThumbnailPaint() { + return mThumbnailPaint; + } + + /** + * Used to precompute the thumbnail paint from the given source bitmap. + */ + private void precomputeThumbnailPaint(Bitmap srcBitmap, int rotation) { + // Lazy loading the thumbnail paint object. + if (mThumbnailPaint == null) { + // Can't create a paint object until the thumbnail bitmap is available. + if (srcBitmap == null) { + return; + } + // The original bitmap should be a square shape. + if (srcBitmap.getWidth() != srcBitmap.getHeight()) { + return; + } + + // Create a bitmap shader for the paint. + BitmapShader shader = + new BitmapShader(srcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (srcBitmap.getWidth() != mViewSize) { + // Create a transformation matrix for the bitmap shader if the size is not + // matched. + RectF srcRect = + new RectF(0.0f, 0.0f, srcBitmap.getWidth(), srcBitmap.getHeight()); + RectF dstRect = new RectF(0.0f, 0.0f, mViewSize, mViewSize); + + Matrix shaderMatrix = new Matrix(); + + // Scale the shader to fit the destination view size. + shaderMatrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.FILL); + + // Rotate the image around the given source rect point. + shaderMatrix.preRotate(rotation, srcRect.width() / 2, srcRect.height() / 2); + + shader.setLocalMatrix(shaderMatrix); + } + + // Create the paint for drawing the thumbnail in a circle. + mThumbnailPaint = new Paint(); + mThumbnailPaint.setAntiAlias(true); + mThumbnailPaint.setShader(shader); + } + } + + /** + * Checks if the request is full-filled. + * + * @return True if both thumbnail animation and ripple animation are finished + */ + public boolean isFinished() { + return mThumbnailAnimationFinished && mRippleAnimationFinished; + } + + /** + * Marks the thumbnail animation is finished. + */ + public void finishThumbnailAnimation() { + mThumbnailAnimationFinished = true; + } + + /** + * Marks the ripple animation is finished. + */ + public void finishRippleAnimation() { + mRippleAnimationFinished = true; + } + + /** + * Updates the thumbnail image. + * + * @param thumbnailBitmap The thumbnail image to be shown. + * @param rotation The orientation of the image in degrees. + */ + public void setThumbnailBitmap(Bitmap thumbnailBitmap, int rotation) { + Bitmap originalBitmap = thumbnailBitmap; + // Crop the image if it is not square. + if (originalBitmap.getWidth() != originalBitmap.getHeight()) { + originalBitmap = cropCenterBitmap(originalBitmap); + } + + precomputeThumbnailPaint(originalBitmap, rotation); + } + + /** + * Obtains a square bitmap by cropping the center of a bitmap. If the given image is + * portrait, the cropped image keeps 100% original width and vertically centered to the + * original image. If the given image is landscape, the cropped image keeps 100% original + * height and horizontally centered to the original image. + * + * @param srcBitmap the bitmap image to be cropped in the center. + * @return a result square bitmap. + */ + private Bitmap cropCenterBitmap(Bitmap srcBitmap) { + int srcWidth = srcBitmap.getWidth(); + int srcHeight = srcBitmap.getHeight(); + Bitmap dstBitmap; + if (srcWidth >= srcHeight) { + dstBitmap = Bitmap.createBitmap(srcBitmap, srcWidth / 2 - srcHeight / 2, 0, + srcHeight, srcHeight); + } else { + dstBitmap = Bitmap.createBitmap(srcBitmap, 0, srcHeight / 2 - srcWidth / 2, + srcWidth, srcWidth); + } + return dstBitmap; + } + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java index bf385e1..d2fef0f 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java @@ -16,28 +16,13 @@ package com.intel.multicamera; -import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.graphics.ImageFormat; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraManager; -import android.hardware.camera2.params.StreamConfigurationMap; -import android.media.CamcorderProfile; import android.os.Bundle; import android.util.Log; -import android.util.Size; -import android.util.SparseArray; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.preference.ListPreference; -import androidx.preference.Preference; import androidx.preference.PreferenceFragment; -import androidx.preference.PreferenceGroup; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; public class SettingsActivity extends AppCompatActivity { private String TAG = "settings"; @@ -52,105 +37,19 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.setDisplayHomeAsUpEnabled(true); } - CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE); - - try { - GlobalVariable.numOfCameras = manager.getCameraIdList().length; - Log.d(TAG, "Inside Settings Total Cameras: " + manager.getCameraIdList().length); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - - if (GlobalVariable.numOfCameras != 0) getSupportedSize(manager); - getFragmentManager() .beginTransaction() .replace(R.id.settings, new SettingsFragment()) .commit(); } - public void getSupportedSize(CameraManager manager) { - try { - String camerId; - - GlobalVariable.camerId = manager.getCameraIdList()[0]; - - camerId = GlobalVariable.camerId; - - CameraCharacteristics characteristics = manager.getCameraCharacteristics(camerId); - - StreamConfigurationMap map = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - if (map == null) { - } - - GlobalVariable.SupportedSizes = - new ArrayList<>(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG))); - - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - public static class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener { public String TAG = "SettingsFragment"; - public static final String SIZE_LARGE = "large"; - public static final String SIZE_MEDIUM = "medium"; - public static final String SIZE_SMALL = "small"; - - public static String DEFAULT_KEY = "capture_list"; - - public String[] mCamcorderProfileNames; - private static final String SIZE_SETTING_STRING_DIMENSION_DELIMITER = "x"; - - public static SparseArray sCachedSelectedVideoQualities = - new SparseArray(3); - private static String mPrefChangedKey = null; - static boolean isPrefChangedKeyChanged = false; - /** The selected {@link CamcorderProfile} qualities. */ - public static class SelectedVideoQualities { - public int large = -1; - public int medium = -1; - public int small = -1; - - public int getFromSetting(String sizeSetting) { - // Sanitize the value to be either small, medium or large. Default - // to the latter. - if (!SIZE_SMALL.equals(sizeSetting) && !SIZE_MEDIUM.equals(sizeSetting)) { - sizeSetting = SIZE_LARGE; - } - - if (SIZE_LARGE.equals(sizeSetting)) { - return large; - } else if (SIZE_MEDIUM.equals(sizeSetting)) { - return medium; - } else { - return small; - } - } - } - - /** Video qualities sorted by size. */ - public static int[] sVideoQualities = - new int[] {// CamcorderProfile.QUALITY_HIGH, - CamcorderProfile.QUALITY_1080P, CamcorderProfile.QUALITY_720P, - CamcorderProfile.QUALITY_480P, CamcorderProfile.QUALITY_CIF, - CamcorderProfile.QUALITY_QVGA, CamcorderProfile.QUALITY_QCIF, - CamcorderProfile.QUALITY_LOW}; - - static SelectedVideoQualities VideoQualities; - public int numOfCameras = GlobalVariable.numOfCameras; - public String camerId = GlobalVariable.camerId; - ; - public List PictureSizes = GlobalVariable.SupportedSizes; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.root_preferences, rootKey); - mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names); - - isPrefChangedKeyChanged = false; + setPreferencesFromResource(R.xml.settings, rootKey); } @Override @@ -160,24 +59,6 @@ public void onResume() { Log.d(TAG, "SettingsFragment onResume"); - /* Put in the summaries for the currently set values. - final PreferenceScreen Multi_Cam = - (PreferenceScreen) findPreference("multi_usb_cam_list");*/ - - setVisibilities(); - - if (numOfCameras > 0) { - VideoQualities = getSelectedVideoQualities(0); - - // Put in the summaries for the currently set values. - final PreferenceGroup Prf_Resolution = - (PreferenceGroup)findPreference("pref_resolution"); - - fillEntriesAndSummaries(Prf_Resolution); - } - - Log.d(TAG, "SettingsFragment onResume end"); - getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener( this); } @@ -191,310 +72,6 @@ public void onPause() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String Key) { - setSummary(findPreference(Key)); - mPrefChangedKey = Key; - isPrefChangedKeyChanged = true; - } - - public static String getchangedPrefKey() { - if (isPrefChangedKeyChanged == true) { - return mPrefChangedKey; - } else { - return DEFAULT_KEY; - } - } - - /** - * Set the summary for the given preference. The given preference needs - * to be a {@link ListPreference}. - */ - private void setSummary(Preference preference) { - if (!(preference instanceof ListPreference)) { - return; - } - - ListPreference listPreference = (ListPreference)preference; - if (listPreference.getKey().equals("capture_list")) { - setSummaryForSelection(PictureSizes, listPreference); - } else if (listPreference.getKey().equals("video_list")) { - setSummaryForSelection(VideoQualities, listPreference); - } else { - listPreference.setSummary(listPreference.getEntry()); - } - } - - /** - * This is used to serialize a size to a string for storage in settings - * - * @param size The size to serialize. - * @return the string to be saved in preferences - */ - private static String sizeToSettingString(Size size) { - return size.getWidth() + SIZE_SETTING_STRING_DIMENSION_DELIMITER + size.getHeight(); - } - - /** - * Sets the summary for the given list preference. - * - * @param displayableSizes The human readable preferred sizes - * @param preference The preference for which to set the summary. - */ - private void setSummaryForSelection(List displayableSizes, - ListPreference preference) { - String setting = preference.getValue(); - if (setting == null || !setting.contains("x")) { - return; - } - Size settingSize = sizeFromSettingString(setting); - if (settingSize == null) { - return; - } - preference.setSummary(getSizeSummaryString(settingSize)); - } - - /** - * Sets the summary for the given list preference. - * - * @param selectedQualities The selected video qualities. - * @param preference The preference for which to set the summary. - */ - private void setSummaryForSelection(SelectedVideoQualities selectedQualities, - ListPreference preference) { - if (selectedQualities == null) { - return; - } - - int selectedQuality = selectedQualities.getFromSetting(preference.getValue()); - Log.d(TAG, "SettingsFragment selectedQuality " + selectedQuality); - preference.setSummary(mCamcorderProfileNames[selectedQuality]); - } - - /** - * This parses a setting string and returns the representative size. - * - * @param sizeSettingString The string that stored in settings to represent a size. - * @return the represented Size. - */ - public static Size sizeFromSettingString(String sizeSettingString) { - if (sizeSettingString == null) { - return null; - } - String[] parts = sizeSettingString.split(SIZE_SETTING_STRING_DIMENSION_DELIMITER); - if (parts.length != 2) { - return null; - } - - try { - int width = Integer.parseInt(parts[0]); - int height = Integer.parseInt(parts[1]); - return new Size(width, height); - } catch (NumberFormatException ex) { - return null; - } - } - - /** - * @param size The photo resolution. - * @return A human readable and translated string for labeling the - * picture size in megapixels. - */ - private String getSizeSummaryString(Size size) { - int width = size.getWidth(); - int height = size.getHeight(); - String result = getResources().getString(R.string.setting_summary_width_and_height, - width, height); - return result; - } - - /** - * Depending on camera availability on the device, this removes settings - * for cameras the device doesn't have. - */ - private void setVisibilities() { - Log.d(TAG, "SettingsFragment setVisibilities"); - - PreferenceGroup Prf_Resolution = (PreferenceGroup)findPreference("pref_resolution"); - PreferenceGroup Prf_Src = (PreferenceGroup)findPreference("pref_Source"); - if (numOfCameras == 0) { - recursiveDelete(Prf_Resolution, findPreference("capture_list")); - recursiveDelete(Prf_Resolution, findPreference("video_list")); - - recursiveDelete(Prf_Src, findPreference("multi_usb_cam_list")); - } - } - - /** - * Recursively go through settings and fill entries and summaries of our - * preferences. - */ - - private void fillEntriesAndSummaries(PreferenceGroup group) { - for (int i = 0; i < group.getPreferenceCount(); ++i) { - Preference pref = group.getPreference(i); - if (pref instanceof PreferenceGroup) { - fillEntriesAndSummaries((PreferenceGroup)pref); - } - setSummary(pref); - setEntries(pref); - } - } - - /** - * Recursively traverses the tree from the given group as the route and - * tries to delete the preference. Traversal stops once the preference - * was found and removed. - */ - private boolean recursiveDelete(PreferenceGroup group, Preference preference) { - Log.d(TAG, "SettingsFragment recursiveDelete"); - - if (group == null) { - Log.d(TAG, "attempting to delete from null preference group"); - return false; - } - if (preference == null) { - Log.d(TAG, "attempting to delete null preference"); - return false; - } - if (group.removePreference(preference)) { - Log.d(TAG, "Removal was successful."); - return true; - } - - for (int i = 0; i < group.getPreferenceCount(); ++i) { - Preference pref = group.getPreference(i); - if (pref instanceof PreferenceGroup) { - if (recursiveDelete((PreferenceGroup)pref, preference)) { - return true; - } - } - } - return false; - } - - /** - * Set the entries for the given preference. The given preference needs - * to be a {@link ListPreference} - */ - private void setEntries(Preference preference) { - if (!(preference instanceof ListPreference)) { - return; - } - - ListPreference listPreference = (ListPreference)preference; - if (listPreference.getKey().equals("capture_list")) { - setEntriesForSelection(PictureSizes, listPreference); - } else if (listPreference.getKey().equals("video_list")) { - setEntriesForSelection(VideoQualities, listPreference); - } - } - - /** - * Sets the entries for the given list preference. - * - * @param selectedSizes The possible S,M,L entries the user can choose - * from. - * @param preference The preference to set the entries for. - */ - private void setEntriesForSelection(List selectedSizes, ListPreference preference) { - if (selectedSizes == null) { - return; - } - - String[] entries = new String[selectedSizes.size()]; - String[] entryValues = new String[selectedSizes.size()]; - for (int i = 0; i < selectedSizes.size(); i++) { - Size size = selectedSizes.get(i); - entries[i] = getSizeSummaryString(size); - entryValues[i] = sizeToSettingString(size); - } - preference.setEntries(entries); - preference.setEntryValues(entryValues); - } - - /** - * Sets the entries for the given list preference. - * - * @param selectedQualities The possible S,M,L entries the user can - * choose from. - * @param preference The preference to set the entries for. - */ - private void setEntriesForSelection(SelectedVideoQualities selectedQualities, - ListPreference preference) { - if (selectedQualities == null) { - return; - } - - // Avoid adding double entries at the bottom of the list which - // indicates that not at least 3 qualities are supported. - ArrayList entries = new ArrayList(); - entries.add(mCamcorderProfileNames[selectedQualities.large]); - if (selectedQualities.medium != selectedQualities.large) { - entries.add(mCamcorderProfileNames[selectedQualities.medium]); - } - if (selectedQualities.small != selectedQualities.medium) { - entries.add(mCamcorderProfileNames[selectedQualities.small]); - } - preference.setEntries(entries.toArray(new String[0])); - } - - /** - * Determines the video quality for large/medium/small for the given camera. - * Returns the one matching the given setting. Defaults to 'large' of the - * qualitySetting does not match either large. medium or small. - * - * @param qualitySetting One of 'large', 'medium', 'small'. - * @param cameraId The ID of the camera for which to get the quality - * setting. - * @return The CamcorderProfile quality setting.} - */ - - static int getVideoQuality(int cameraId, String qualitySetting) { - return getSelectedVideoQualities(cameraId).getFromSetting(qualitySetting); - } - - static SelectedVideoQualities getSelectedVideoQualities(int cameraId) { - if (sCachedSelectedVideoQualities.get(cameraId) != null) { - return sCachedSelectedVideoQualities.get(cameraId); - } - - // Go through the sizes in descending order, see if they are supported, - // and set large/medium/small accordingly. - // If no quality is supported at all, the first call to - // getNextSupportedQuality will throw an exception. - // If only one quality is supported, then all three selected qualities - // will be the same. - int largeIndex = getNextSupportedVideoQualityIndex(cameraId, -1); - int mediumIndex = getNextSupportedVideoQualityIndex(cameraId, largeIndex); - int smallIndex = getNextSupportedVideoQualityIndex(cameraId, mediumIndex); - VideoQualities = new SelectedVideoQualities(); - VideoQualities.large = sVideoQualities[largeIndex]; - VideoQualities.medium = sVideoQualities[mediumIndex]; - VideoQualities.small = sVideoQualities[smallIndex]; - sCachedSelectedVideoQualities.put(cameraId, VideoQualities); - return VideoQualities; - } - - /* - * Starting from 'start' this method returns the next supported video - * quality. - */ - static int getNextSupportedVideoQualityIndex(int cameraId, int start) { - for (int i = start + 1; i < sVideoQualities.length; ++i) { - if (CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) { - // We found a new supported quality. - return i; - } - } - - // Failed to find another supported quality. - if (start < 0 || start >= sVideoQualities.length) { - // This means we couldn't find any supported quality. - throw new IllegalArgumentException("Could not find supported video qualities."); - } - - // We previously found a larger supported size. In this edge case, just - // return the same index as the previous size. - return start; } } -} +} \ No newline at end of file diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsPrefUtil.java b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsPrefUtil.java new file mode 100644 index 0000000..7d3b24c --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsPrefUtil.java @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (c) 2019 Intel Corporation. + * + * 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.content.Context; +import android.content.SharedPreferences; +import android.graphics.ImageFormat; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.CamcorderProfile; +import android.os.Bundle; +import android.util.Log; +import android.util.Size; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragment; +import androidx.preference.PreferenceGroup; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SettingsPrefUtil + extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + public static String TAG = "SettingsPrefUtil"; + + public static final String SIZE_LARGE = "large"; + public static final String SIZE_MEDIUM = "medium"; + public static final String SIZE_SMALL = "small"; + + public String[] mCamcorderProfileNames; + private static final String SIZE_SETTING_STRING_DIMENSION_DELIMITER = "x"; + + public static SparseArray sCachedSelectedVideoQualities = + new SparseArray<>(3); + + private String CameraId; + private int root_preferences; + private String pref_resolution; + private String video_list, capture_list; + public List PictureSizes; + static final Size SIZE_4K = new Size(3840, 2160); + static final Size SIZE_1080P = new Size(1920, 1080); + static final Size SIZE_720P = new Size(1280, 720); + static final Size SIZE_480P = new Size(640, 480); + static final Size SIZE_240P = new Size(320, 240); + + /** + * The selected {@link CamcorderProfile} qualities. + */ + + public static class SelectedVideoQualities { + public int large = -1; + public int medium = -1; + public int small = -1; + + public int getFromSetting(String sizeSetting) { + // Sanitize the value to be either small, medium or large. Default + // to the latter. + if (!SIZE_SMALL.equals(sizeSetting) && !SIZE_MEDIUM.equals(sizeSetting)) { + sizeSetting = SIZE_LARGE; + } + + if (SIZE_LARGE.equals(sizeSetting)) { + return large; + } else if (SIZE_MEDIUM.equals(sizeSetting)) { + return medium; + } else { + return small; + } + } + } + + public void getSupportedSize(String camerId) { + try { + List ImageDimentions; + + CameraManager manager = + (CameraManager)getActivity().getSystemService(Context.CAMERA_SERVICE); + + CameraCharacteristics characteristics = manager.getCameraCharacteristics(camerId); + + StreamConfigurationMap map = + characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (map == null) { + } + + ImageDimentions = new ArrayList<>(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG))); + + PictureSizes = new ArrayList(Arrays.asList()); + + Log.d(TAG, " @@ getSupportedSize onResume @@" + CameraId); + + for (Size size : ImageDimentions) { + if (size.equals(SIZE_240P)) { + PictureSizes.add(SIZE_240P); + + } else if (size.equals(SIZE_480P)) { + PictureSizes.add(SIZE_480P); + + } else if (size.equals(SIZE_720P)) { + PictureSizes.add(SIZE_720P); + + } else if (size.equals(SIZE_1080P)) { + PictureSizes.add(SIZE_1080P); + + } else if (size.equals(SIZE_4K)) { + PictureSizes.add(SIZE_4K); + } + } + + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + /** + * Video qualities sorted by size. + */ + public static int[] sVideoQualities = + new int[] {// CamcorderProfile.QUALITY_HIGH, + CamcorderProfile.QUALITY_1080P, CamcorderProfile.QUALITY_720P, + CamcorderProfile.QUALITY_480P, CamcorderProfile.QUALITY_CIF, + CamcorderProfile.QUALITY_QVGA, CamcorderProfile.QUALITY_QCIF, + CamcorderProfile.QUALITY_LOW}; + + static SelectedVideoQualities VideoQualities; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + switch (getArguments().getInt("root_preferences")) { + case R.xml.root_preferences: + root_preferences = R.xml.root_preferences; + CameraId = getArguments().getString("Camera_id"); + break; + case R.xml.root_preferences_1: + root_preferences = R.xml.root_preferences_1; + CameraId = getArguments().getString("Camera_id"); + break; + case R.xml.root_preferences_2: + root_preferences = R.xml.root_preferences_2; + CameraId = getArguments().getString("Camera_id"); + break; + case R.xml.root_preferences_3: + root_preferences = R.xml.root_preferences_3; + CameraId = getArguments().getString("Camera_id"); + break; + default: + break; + } + + setPreferencesFromResource(root_preferences, rootKey); + mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v; + v = super.onCreateView(inflater, container, savedInstanceState); + + switch (getArguments().getString("pref_resolution")) { + case "pref_resolution": + pref_resolution = "pref_resolution"; + break; + case "pref_resolution_1": + pref_resolution = "pref_resolution_1"; + break; + case "pref_resolution_2": + pref_resolution = "pref_resolution_2"; + break; + case "pref_resolution_3": + pref_resolution = "pref_resolution_3"; + break; + default: + break; + } + + switch (getArguments().getString("video_list")) { + case "video_list": + video_list = "video_list"; + break; + case "video_list_1": + video_list = "video_list_1"; + break; + case "video_list_2": + video_list = "video_list_2"; + break; + case "video_list_3": + video_list = "video_list_3"; + break; + default: + break; + } + + switch (getArguments().getString("capture_list")) { + case "capture_list": + capture_list = "capture_list"; + break; + case "capture_list_1": + capture_list = "capture_list_1"; + break; + case "capture_list_2": + capture_list = "capture_list_2"; + break; + case "capture_list_3": + capture_list = "capture_list_3"; + break; + + default: + break; + } + + VideoQualities = getSelectedVideoQualities(0); + + getSupportedSize(CameraId); + + // Put in the summaries for the currently set values. + final PreferenceGroup Prf_Resolution = (PreferenceGroup)findPreference(pref_resolution); + + fillEntriesAndSummaries(Prf_Resolution); + + return v; + } + + @Override + public void onResume() { + super.onResume(); + // final Activity activity = this.getActivity(); + Log.d(TAG, " @@ SettingsFragment onResume @@" + CameraId); + + VideoQualities = getSelectedVideoQualities(0); + + getSupportedSize(CameraId); + + // Put in the summaries for the currently set values. + final PreferenceGroup Prf_Resolution = (PreferenceGroup)findPreference(pref_resolution); + + fillEntriesAndSummaries(Prf_Resolution); + + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + + Log.d(TAG, "SettingsFragment onResume end"); + } + + @Override + public void onPause() { + super.onPause(); + // final Activity activity = this.getActivity(); + Log.d(TAG, " @@ SettingsFragment onPause @@" + CameraId); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener( + this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(TAG, " @@ SettingsFragment onDestroy @@" + CameraId); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String Key) { + setSummary(findPreference(Key)); + + switch (Key) { + case "capture_list": + sharedPreferences.edit().putString("pref_resolution", Key).apply(); + break; + case "capture_list_1": + sharedPreferences.edit().putString("pref_resolution_1", Key).apply(); + break; + case "capture_list_2": + sharedPreferences.edit().putString("pref_resolution_2", Key).apply(); + break; + case "capture_list_3": + sharedPreferences.edit().putString("pref_resolution_3", Key).apply(); + break; + case "video_list": + sharedPreferences.edit().putString("pref_resolution", Key).apply(); + break; + case "video_list_1": + sharedPreferences.edit().putString("pref_resolution_1", Key).apply(); + break; + case "video_list_2": + sharedPreferences.edit().putString("pref_resolution_2", Key).apply(); + break; + case "video_list_3": + sharedPreferences.edit().putString("pref_resolution_3", Key).apply(); + break; + default: + break; + } + } + + /** + * Set the summary for the given preference. The given preference needs + * to be a {@link ListPreference}. + */ + private void setSummary(Preference preference) { + if (!(preference instanceof ListPreference)) { + return; + } + + ListPreference listPreference = (ListPreference)preference; + if (listPreference.getKey().equals(capture_list)) { + setSummaryForSelection(PictureSizes, listPreference); + } else if (listPreference.getKey().equals(video_list)) { + setSummaryForSelection(VideoQualities, listPreference); + } else { + listPreference.setSummary(listPreference.getEntry()); + } + } + + /** + * This is used to serialize a size to a string for storage in settings + * + * @param size The size to serialize. + * @return the string to be saved in preferences + */ + private static String sizeToSettingString(Size size) { + return size.getWidth() + SIZE_SETTING_STRING_DIMENSION_DELIMITER + size.getHeight(); + } + + /** + * Sets the summary for the given list preference. + * + * @param displayableSizes The human readable preferred sizes + * @param preference The preference for which to set the summary. + */ + private void setSummaryForSelection(List displayableSizes, ListPreference preference) { + String setting = preference.getValue(); + if (setting == null || !setting.contains("x")) { + return; + } + Size settingSize = sizeFromSettingString(setting); + if (settingSize == null) { + return; + } + preference.setSummary(getSizeSummaryString(settingSize)); + } + + /** + * Sets the summary for the given list preference. + * + * @param selectedQualities The selected video qualities. + * @param preference The preference for which to set the summary. + */ + private void setSummaryForSelection(SelectedVideoQualities selectedQualities, + ListPreference preference) { + if (selectedQualities == null) { + return; + } + + int selectedQuality = selectedQualities.getFromSetting(preference.getValue()); + Log.d(TAG, "SettingsFragment selectedQuality " + selectedQuality); + preference.setSummary(mCamcorderProfileNames[selectedQuality]); + } + + /** + * This parses a setting string and returns the representative size. + * + * @param sizeSettingString The string that stored in settings to represent a size. + * @return the represented Size. + */ + public static Size sizeFromSettingString(String sizeSettingString) { + if (sizeSettingString == null) { + return null; + } + String[] parts = sizeSettingString.split(SIZE_SETTING_STRING_DIMENSION_DELIMITER); + if (parts.length != 2) { + return null; + } + + try { + int width = Integer.parseInt(parts[0]); + int height = Integer.parseInt(parts[1]); + return new Size(width, height); + } catch (NumberFormatException ex) { + return null; + } + } + + /** + * @param size The photo resolution. + * @return A human readable and translated string for labeling the + * picture size in megapixels. + */ + private String getSizeSummaryString(Size size) { + int width = size.getWidth(); + int height = size.getHeight(); + String result = + getResources().getString(R.string.setting_summary_width_and_height, width, height); + return result; + } + + /** + * Depending on camera availability on the device, this removes settings + * for cameras the device doesn't have. + */ + private void setVisibilities() { + Log.d(TAG, "SettingsFragment setVisibilities"); + + PreferenceGroup Prf_Resolution = (PreferenceGroup)findPreference("pref_resolution"); + PreferenceGroup Prf_Src = (PreferenceGroup)findPreference("pref_Source"); + if (CameraId == null) { + recursiveDelete(Prf_Resolution, findPreference("capture_list")); + recursiveDelete(Prf_Resolution, findPreference("video_list")); + + recursiveDelete(Prf_Src, findPreference("multi_usb_cam_list")); + } + } + + /** + * Recursively go through settings and fill entries and summaries of our + * preferences. + */ + + private void fillEntriesAndSummaries(PreferenceGroup group) { + for (int i = 0; i < group.getPreferenceCount(); ++i) { + Preference pref = group.getPreference(i); + if (pref instanceof PreferenceGroup) { + fillEntriesAndSummaries((PreferenceGroup)pref); + } + setSummary(pref); + setEntries(pref); + } + } + + /** + * Recursively traverses the tree from the given group as the route and + * tries to delete the preference. Traversal stops once the preference + * was found and removed. + */ + private boolean recursiveDelete(PreferenceGroup group, Preference preference) { + Log.d(TAG, "SettingsFragment recursiveDelete"); + + if (group == null) { + Log.d(TAG, "attempting to delete from null preference group"); + return false; + } + if (preference == null) { + Log.d(TAG, "attempting to delete null preference"); + return false; + } + if (group.removePreference(preference)) { + Log.d(TAG, "Removal was successful."); + return true; + } + + for (int i = 0; i < group.getPreferenceCount(); ++i) { + Preference pref = group.getPreference(i); + if (pref instanceof PreferenceGroup) { + if (recursiveDelete((PreferenceGroup)pref, preference)) { + return true; + } + } + } + return false; + } + + /** + * Set the entries for the given preference. The given preference needs + * to be a {@link ListPreference} + */ + private void setEntries(Preference preference) { + if (!(preference instanceof ListPreference)) { + return; + } + + ListPreference listPreference = (ListPreference)preference; + if (listPreference.getKey().equals(capture_list)) { + setEntriesForSelection(PictureSizes, listPreference); + } else if (listPreference.getKey().equals(video_list)) { + setEntriesForSelection(VideoQualities, listPreference); + } + } + + /** + * Sets the entries for the given list preference. + * + * @param selectedSizes The possible S,M,L entries the user can choose + * from. + * @param preference The preference to set the entries for. + */ + private void setEntriesForSelection(List selectedSizes, ListPreference preference) { + if (selectedSizes == null) { + return; + } + + String[] entries = new String[selectedSizes.size()]; + String[] entryValues = new String[selectedSizes.size()]; + for (int i = 0; i < selectedSizes.size(); i++) { + Size size = selectedSizes.get(i); + entries[i] = getSizeSummaryString(size); + entryValues[i] = sizeToSettingString(size); + } + preference.setEntries(entries); + preference.setEntryValues(entryValues); + } + + /** + * Sets the entries for the given list preference. + * + * @param selectedQualities The possible S,M,L entries the user can + * choose from. + * @param preference The preference to set the entries for. + */ + private void setEntriesForSelection(SelectedVideoQualities selectedQualities, + ListPreference preference) { + if (selectedQualities == null) { + return; + } + + // Avoid adding double entries at the bottom of the list which + // indicates that not at least 3 qualities are supported. + ArrayList entries = new ArrayList(); + entries.add(mCamcorderProfileNames[selectedQualities.large]); + if (selectedQualities.medium != selectedQualities.large) { + entries.add(mCamcorderProfileNames[selectedQualities.medium]); + } + if (selectedQualities.small != selectedQualities.medium) { + entries.add(mCamcorderProfileNames[selectedQualities.small]); + } + preference.setEntries(entries.toArray(new String[0])); + } + + /** + * Determines the video quality for large/medium/small for the given camera. + * Returns the one matching the given setting. Defaults to 'large' of the + * qualitySetting does not match either large. medium or small. + * + * @param qualitySetting One of 'large', 'medium', 'small'. + * @param cameraId The ID of the camera for which to get the quality + * setting. + * @return The CamcorderProfile quality setting.} + */ + + static int getVideoQuality(int cameraId, String qualitySetting) { + return getSelectedVideoQualities(cameraId).getFromSetting(qualitySetting); + } + + static SelectedVideoQualities getSelectedVideoQualities(int cameraId) { + if (sCachedSelectedVideoQualities.get(cameraId) != null) { + return sCachedSelectedVideoQualities.get(cameraId); + } + + // Go through the sizes in descending order, see if they are supported, + // and set large/medium/small accordingly. + // If no quality is supported at all, the first call to + // getNextSupportedQuality will throw an exception. + // If only one quality is supported, then all three selected qualities + // will be the same. + int largeIndex = getNextSupportedVideoQualityIndex(cameraId, -1); + int mediumIndex = getNextSupportedVideoQualityIndex(cameraId, largeIndex); + int smallIndex = getNextSupportedVideoQualityIndex(cameraId, mediumIndex); + VideoQualities = new SelectedVideoQualities(); + VideoQualities.large = sVideoQualities[largeIndex]; + VideoQualities.medium = sVideoQualities[mediumIndex]; + VideoQualities.small = sVideoQualities[smallIndex]; + sCachedSelectedVideoQualities.put(cameraId, VideoQualities); + return VideoQualities; + } + + /* + * Starting from 'start' this method returns the next supported video + * quality. + */ + static int getNextSupportedVideoQualityIndex(int cameraId, int start) { + for (int i = start + 1; i < sVideoQualities.length; ++i) { + if (CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) { + // We found a new supported quality. + return i; + } + } + + // Failed to find another supported quality. + if (start < 0 || start >= sVideoQualities.length) { + // This means we couldn't find any supported quality. + throw new IllegalArgumentException("Could not find supported video qualities."); + } + + // We previously found a larger supported size. In this edge case, just + // return the same index as the previous size. + return start; + } +} \ No newline at end of file diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/SurfaceUtil.java b/camera/MultiCameraApplication/java/com/intel/multicamera/SurfaceUtil.java new file mode 100644 index 0000000..16ad523 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SurfaceUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Intel Corporation. + * + * 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.graphics.SurfaceTexture; +import android.opengl.GLES20; +import javax.microedition.khronos.egl.*; + +public class SurfaceUtil { + private static final int[] ATTRIB_LIST = new int[] { + EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, EGL10.EGL_NONE, + }; + + // http://stackoverflow.com/a/21564236/2681195 + public static void clear(SurfaceTexture mTexture) { + EGL10 egl = (EGL10)EGLContext.getEGL(); + EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + int[] version = new int[2]; + egl.eglInitialize(display, version); + + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfig = new int[1]; + egl.eglChooseConfig(display, ATTRIB_LIST, configs, 1, numConfig); + + EGLSurface surface = egl.eglCreateWindowSurface(display, configs[0], mTexture, null); + EGLContext context = egl.eglCreateContext(display, configs[0], EGL10.EGL_NO_CONTEXT, null); + + egl.eglMakeCurrent(display, surface, surface, context); + + GLES20.glClearColor(0, 0, 1, 0); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + + egl.eglSwapBuffers(display, surface); + egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + egl.eglDestroyContext(display, context); + egl.eglDestroySurface(display, surface); + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/Thumbnail.java b/camera/MultiCameraApplication/java/com/intel/multicamera/Thumbnail.java new file mode 100644 index 0000000..1562766 --- /dev/null +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/Thumbnail.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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.graphics.Bitmap; +import android.media.MediaMetadataRetriever; +import java.io.FileDescriptor; + +public class Thumbnail { + public static Bitmap createVideoThumbnailBitmap(FileDescriptor fd, int targetWidth) { + return createVideoThumbnailBitmap(null, fd, targetWidth); + } + + public static Bitmap createVideoThumbnailBitmap(String filePath, int targetWidth) { + return createVideoThumbnailBitmap(filePath, null, targetWidth); + } + + private static Bitmap createVideoThumbnailBitmap(String filePath, FileDescriptor fd, + int targetWidth) { + Bitmap bitmap = null; + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + if (filePath != null) { + retriever.setDataSource(filePath); + } else { + retriever.setDataSource(fd); + } + bitmap = retriever.getFrameAtTime(-1); + } catch (IllegalArgumentException ex) { + // Assume this is a corrupt video file + } catch (RuntimeException ex) { + // Assume this is a corrupt video file. + } finally { + try { + retriever.release(); + } catch (RuntimeException ex) { + // Ignore failures while cleaning up. + } + } + if (bitmap == null) return null; + + // Scale down the bitmap if it is bigger than we need. + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + if (width > targetWidth) { + float scale = (float)targetWidth / width; + int w = Math.round(scale * width); + int h = Math.round(scale * height); + bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true); + } + return bitmap; + } +} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java b/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java deleted file mode 100644 index 21e73c2..0000000 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/TopRightCam.java +++ /dev/null @@ -1,649 +0,0 @@ -/* - * Copyright 2014 The Android Open Source Project - * Copyright (c) 2019 Intel Corporation. - * - * 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.annotation.SuppressLint; -import android.app.Activity; -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; -import android.media.CamcorderProfile; -import android.media.Image; -import android.media.ImageReader; -import android.media.MediaRecorder; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.provider.MediaStore; -import android.util.Log; -import android.util.Size; -import android.util.SparseIntArray; -import android.view.Surface; -import android.view.TextureView; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.preference.PreferenceManager; -import java.io.*; -import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class TopRightCam { - Activity mActivity; - private static final String TAG = "TopRightCam"; - private String mNextVideoAbsolutePath; - private CamcorderProfile mProfile; - /** - * An {@link AutoFitTextureView} for camera preview. - */ - private AutoFitTextureView textureView; - private Button takePictureButton, TakeVideoButton; - - private MediaRecorder mMediaRecorder; - private String cameraId; - protected CameraDevice cameraDevice; - protected CameraCaptureSession cameraCaptureSessions; - protected CaptureRequest captureRequest; - protected CaptureRequest.Builder captureRequestBuilder; - private Size imageDimension, previewSize; - private ImageReader imageReader; - private File file; - 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; - /** - * Whether the app is recording video now - */ - private boolean mIsRecordingVideo; - - // The video file that the hardware camera is about to record into - // (or is recording into. - private String mVideoFilename, mPictureFilename; - 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); - ORIENTATIONS.append(Surface.ROTATION_180, 270); - ORIENTATIONS.append(Surface.ROTATION_270, 180); - } - - public TopRightCam(Activity activity, AutoFitTextureView textureView, Button PictureButton, - Button RecordButton) { - Log.e(TAG, "constructor called"); - this.mActivity = activity; - this.textureView = textureView; - this.textureView.setSurfaceTextureListener(textureListener); - this.ClickListeners(PictureButton, RecordButton); - this.settings = PreferenceManager.getDefaultSharedPreferences(activity); - } - - public void ClickListeners(Button PictureButton, Button RecordButton) { - TakePicureOnClicked(PictureButton); - - StartVideoRecording(RecordButton); - } - - private void TakePicureOnClicked(Button PictureButton) { - takePictureButton = PictureButton; - if (takePictureButton == null) return; - - takePictureButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - takePicture(); - Utils.broadcastNewPicture(mActivity.getApplicationContext(), mCurrentPictureValues); - } - }); - } - - private void StartVideoRecording(Button RecordButton) { - TakeVideoButton = RecordButton; - TakeVideoButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - // Intent i = new Intent(HomeScreenActivity.this, CameraActivity.class); - System.out.println(" onCreate Record0"); - if (mIsRecordingVideo) { - stopRecordingVideo(); - Utils.broadcastNewVideo(mActivity.getApplicationContext(), mCurrentVideoValues); - takePictureButton.setVisibility(View.VISIBLE); - } else { - startRecordingVideo(); - takePictureButton.setVisibility(View.GONE); - } - } - }); - } - - TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - // open your camera here - 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) { - return false; - } - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - } - }; - - 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(); - - 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); - - } catch (CameraAccessException e) { - e.printStackTrace(); - } - Log.e(TAG, "openCamera X"); - } - - private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { - @Override - public void onOpened(CameraDevice camera) { - // This is called when the camera is open - Log.e(TAG, "onOpened"); - cameraDevice = camera; - createCameraPreview(); - } - @Override - public void onDisconnected(CameraDevice camera) { - Log.e(TAG, "onDisconnected"); - closeCamera(); - } - @Override - public void onError(CameraDevice camera, int error) { - 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")); - 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); - - if (Key.compareTo("video_list") == 0) { - texture.setDefaultBufferSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); - } else { - texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()); - } - - Surface surface = new Surface(texture); - captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); - captureRequestBuilder.addTarget(surface); - cameraDevice.createCaptureSession( - Arrays.asList(surface), new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession cameraCaptureSession) { - // The camera is already closed - if (null == cameraDevice) { - return; - } - // When the session is ready, we start displaying the preview. - cameraCaptureSessions = cameraCaptureSession; - updatePreview(); - } - @Override - public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { - Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT) - .show(); - } - }, null); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - public void releaseMedia() { - if (null != mMediaRecorder) { - try { - mMediaRecorder.stop(); - } catch (IllegalStateException ex) { - Log.d(TAG, "Stop called before start"); - } - mMediaRecorder.reset(); - mMediaRecorder.release(); - mMediaRecorder = null; - } - } - - public void closeCamera() { - closePreviewSession(); - if (null != cameraDevice) { - cameraDevice.close(); - cameraDevice = null; - } - if (null != imageReader) { - imageReader.close(); - imageReader = null; - } - releaseMedia(); - stopBackgroundThread(); - } - - /** - * Starts a background thread and its {@link Handler}. - */ - private void startBackgroundThread() { - mBackgroundThread = new HandlerThread("Camera_1"); - mBackgroundThread.start(); - mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); - } - - /** - * Stops the background thread and its {@link Handler}. - */ - private void stopBackgroundThread() { - if (mBackgroundThread != null) { - mBackgroundThread.quitSafely(); - try { - mBackgroundThread.join(); - mBackgroundThread = null; - mBackgroundHandler = null; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - protected void updatePreview() { - if (null == cameraDevice) { - Log.e(TAG, "updatePreview error, return"); - } - captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - HandlerThread thread = new HandlerThread("Camera Preview"); - thread.start(); - Handler handler = new Handler(thread.getLooper()); - try { - cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, handler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - /** - * 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"); - return; - } - - 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); - outputSurfaces.add(reader.getSurface()); - outputSurfaces.add(new Surface(textureView.getSurfaceTexture())); - captureRequestBuilder = - cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); - captureRequestBuilder.addTarget(reader.getSurface()); - captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, - CameraMetadata.CONTROL_MODE_AUTO); - // Orientation - int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); - - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE); - if (fileDetails == null || fileDetails.length < 5) { - Log.e(TAG, "Invalid file details"); - return; - } - mPictureFilename = fileDetails[3]; - mCurrentPictureValues = - Utils.getContentValues(Utils.MEDIA_TYPE_IMAGE, fileDetails, - imageDimension.getWidth(), imageDimension.getHeight()); - - file = new File(mPictureFilename); - - ImageReader.OnImageAvailableListener readerListener = - new ImageReader.OnImageAvailableListener() { - @Override - public void onImageAvailable(ImageReader reader) { - Image image = null; - try { - image = reader.acquireLatestImage(); - ByteBuffer buffer = image.getPlanes()[0].getBuffer(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - jpegLength = bytes; - mCurrentPictureValues.put(MediaStore.Images.ImageColumns.SIZE, - jpegLength); - - save(bytes); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (image != null) { - image.close(); - } - } - } - - private void save(byte[] bytes) throws IOException { - OutputStream output = null; - try { - output = new FileOutputStream(file); - output.write(bytes); - } finally { - if (null != output) { - output.close(); - } - } - } - }; - reader.setOnImageAvailableListener(readerListener, mBackgroundHandler); - final CameraCaptureSession.CaptureCallback captureListener = - new CameraCaptureSession.CaptureCallback() { - @Override - public void onCaptureCompleted(CameraCaptureSession session, - CaptureRequest request, - TotalCaptureResult result) { - super.onCaptureCompleted(session, request, result); - Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show(); - - createCameraPreview(); - } - }; - cameraDevice.createCaptureSession( - outputSurfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - try { - session.capture(captureRequestBuilder.build(), captureListener, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - @Override - public void onConfigureFailed(CameraCaptureSession session) { - } - }, mBackgroundHandler); - } catch (CameraAccessException e) { - e.printStackTrace(); - } - } - - /* Recording Start*/ - private void startRecordingVideo() { - 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; - texture.setDefaultBufferSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); - - captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); - List surfaces = new ArrayList<>(); - - // Set up Surface for the camera preview - Surface previewSurface = new Surface(texture); - surfaces.add(previewSurface); - captureRequestBuilder.addTarget(previewSurface); - - // Set up Surface for the MediaRecorder - Surface recorderSurface = mMediaRecorder.getSurface(); - surfaces.add(recorderSurface); - captureRequestBuilder.addTarget(recorderSurface); - - // Start a capture session - // Once the session starts, we can update the UI and start recording - cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(@NonNull CameraCaptureSession camCaptureSession) { - cameraCaptureSessions = camCaptureSession; - updatePreview(); - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - // UI - TakeVideoButton.setText(R.string.stop); - mIsRecordingVideo = true; - - // Start recording - mMediaRecorder.start(); - } - }); - } - - @Override - public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { - if (null != mActivity) { - Toast.makeText(mActivity, "Failed", Toast.LENGTH_SHORT).show(); - } - } - }, mBackgroundHandler); - } catch (CameraAccessException | IOException e) { - e.printStackTrace(); - } - } - - private void setUpMediaRecorder() throws IOException { - if (null == mActivity) { - return; - } - - mMediaRecorder = new MediaRecorder(); - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); - mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - - String fileDetails[] = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO); - if (fileDetails == null || fileDetails.length < 5) { - Log.e(TAG, "Invalid file details"); - return; - } - mVideoFilename = fileDetails[3]; - mCurrentVideoValues = - 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); - releaseMedia(); - throw new RuntimeException(ex); - } - } - - private void closePreviewSession() { - System.out.println(" closePreviewSession"); - if (cameraCaptureSessions != null) { - cameraCaptureSessions.close(); - cameraCaptureSessions = null; - } - } - - private void stopRecordingVideo() { - mIsRecordingVideo = false; - TakeVideoButton.setText(R.string.record); - - // Stop recording - releaseMedia(); - - if (null != mActivity) { - Toast.makeText(mActivity, "Video saved: " + mVideoFilename, Toast.LENGTH_SHORT).show(); - Log.d(TAG, "Video saved: " + mVideoFilename); - } - mVideoFilename = null; - - createCameraPreview(); - } -} diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java index 32a3289..a4c4b09 100644 --- a/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java +++ b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 2019 Intel Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,28 +17,29 @@ package com.intel.multicamera; -import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.*; -import android.content.pm.PackageManager; -import android.graphics.ImageFormat; -import android.hardware.camera2.*; -import android.hardware.camera2.params.StreamConfigurationMap; -import android.media.CamcorderProfile; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.graphics.Point; import android.net.Uri; import android.os.Environment; +import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.util.Log; -import android.util.Size; -import android.util.SparseIntArray; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; +import javax.microedition.khronos.opengles.GL11; public class Utils { private static final String TAG = "Utils"; @@ -48,13 +50,28 @@ public class Utils { public static final String IMAGE_FILE_NAME_FORMAT = "'IMG'_yyyyMMdd_HHmmss"; public static final String VIDEO_FILE_NAME_FORMAT = "'VID'_yyyyMMdd_HHmmss"; - /** See android.hardware.Camera.ACTION_NEW_PICTURE. */ + /** + * See android.hardware.Camera.ACTION_NEW_PICTURE. + */ public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE"; - /** See android.hardware.Camera.ACTION_NEW_VIDEO. */ + /** + * See android.hardware.Camera.ACTION_NEW_VIDEO. + */ public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; private static final String VIDEO_BASE_URI = "content://media/external/video/media"; + private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs. + + private static Uri mCurrentPictureUri, mCurrentVideoUri; + + /** + * Has to be in sync with the receiving MovieActivity. + */ + public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back"; + + private static final int DOWN_SAMPLE_FACTOR = 4; + @SuppressLint("SimpleDateFormat") public static File createOutputmediaStorageDir() { // To be safe, you should check that the SDCard is mounted @@ -100,9 +117,19 @@ public static void broadcastNewPicture(Context context, ContentValues values) { Log.v(TAG, "Current Picture URI: " + uri); } + mCurrentPictureUri = uri; + context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri)); } + public static Uri getCurrentPictureUri() { + return mCurrentPictureUri; + } + + public static Uri getCurrentVideoUri() { + return mCurrentVideoUri; + } + public static void broadcastNewVideo(Context context, ContentValues values) { Uri uri = null; ContentResolver resolver = context.getContentResolver(); @@ -117,6 +144,7 @@ public static void broadcastNewVideo(Context context, ContentValues values) { } finally { Log.v(TAG, "Current video URI: " + uri); } + mCurrentVideoUri = uri; context.sendBroadcast(new Intent(ACTION_NEW_VIDEO, uri)); } @@ -164,7 +192,7 @@ public static String[] generateFileDetails(int type) { } public static ContentValues getContentValues(int type, String fileDetails[], int width, - int height) { + int height, long duration, long size) { if (fileDetails.length < 5) { Log.e(TAG, "Invalid file details"); return null; @@ -185,6 +213,8 @@ public static ContentValues getContentValues(int type, String fileDetails[], int contentValue.put(MediaStore.Images.ImageColumns.DATA, fileDetails[3]); contentValue.put(MediaStore.MediaColumns.WIDTH, width); contentValue.put(MediaStore.MediaColumns.HEIGHT, height); + contentValue.put(MediaStore.Images.ImageColumns.SIZE, size); + } else if (MEDIA_TYPE_VIDEO == type) { contentValue = new ContentValues(9); contentValue.put(MediaStore.Video.Media.TITLE, fileDetails[0]); @@ -198,7 +228,329 @@ public static ContentValues getContentValues(int type, String fileDetails[], int contentValue.put(MediaStore.Video.Media.HEIGHT, height); contentValue.put(MediaStore.Video.Media.RESOLUTION, Integer.toString(width) + "x" + Integer.toString(height)); + contentValue.put(MediaStore.Video.Media.DURATION, duration); + contentValue.put(MediaStore.Video.Media.SIZE, size); } return contentValue; } + + /** + * Returns the maximum video recording duration (in milliseconds). + */ + public static int getMaxVideoDuration(Context context) { + int duration = 0; // in milliseconds, 0 means unlimited. + try { + duration = + 0; // context.getResources().getInteger(R.integer.max_video_recording_length); + } catch (Resources.NotFoundException ex) { + } + return duration; + } + + public static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) { + long seconds = milliSeconds / 1000; // round down to compute seconds + long minutes = seconds / 60; + long hours = minutes / 60; + long remainderMinutes = minutes - (hours * 60); + long remainderSeconds = seconds - (minutes * 60); + + StringBuilder timeStringBuilder = new StringBuilder(); + + // Hours + if (hours > 0) { + if (hours < 10) { + timeStringBuilder.append('0'); + } + timeStringBuilder.append(hours); + + timeStringBuilder.append(':'); + } + + // Minutes + if (remainderMinutes < 10) { + timeStringBuilder.append('0'); + } + timeStringBuilder.append(remainderMinutes); + timeStringBuilder.append(':'); + + // Seconds + if (remainderSeconds < 10) { + timeStringBuilder.append('0'); + } + timeStringBuilder.append(remainderSeconds); + + // Centi seconds + if (displayCentiSeconds) { + timeStringBuilder.append('.'); + long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10; + if (remainderCentiSeconds < 10) { + timeStringBuilder.append('0'); + } + timeStringBuilder.append(remainderCentiSeconds); + } + + return timeStringBuilder.toString(); + } + + /** + * Load the thumbnail of an image from an {@link InputStream}. + * + * @param stream The input stream of the image. + * @param imageWidth Image width. + * @param imageHeight Image height. + * @param widthBound The bound of the width of the decoded image. + * @param heightBound The bound of the height of the decoded image. + * @param orientation The orientation of the image. The image will be rotated + * clockwise in degrees. + * @param maximumPixels The bound for the number of pixels of the decoded image. + * @return {@code null} if the decoding failed. + */ + public static Bitmap loadImageThumbnailFromStream(InputStream stream, int imageWidth, + int imageHeight, int widthBound, + int heightBound, int orientation, + int maximumPixels) { + /** 32K buffer. */ + byte[] decodeBuffer = new byte[32 * 1024]; + + if (orientation % 180 != 0) { + int dummy = imageHeight; + imageHeight = imageWidth; + imageWidth = dummy; + } + + // Generate Bitmap of maximum size that fits into widthBound x heightBound. + // Algorithm: start with full size and step down in powers of 2. + int targetWidth = imageWidth; + int targetHeight = imageHeight; + int sampleSize = 1; + while (targetHeight > heightBound || targetWidth > widthBound || + targetHeight > GL11.GL_MAX_TEXTURE_SIZE || targetWidth > GL11.GL_MAX_TEXTURE_SIZE || + targetHeight * targetWidth > maximumPixels) { + sampleSize <<= 1; + targetWidth = imageWidth / sampleSize; + targetHeight = imageWidth / sampleSize; + } + + // For large (> MAXIMUM_TEXTURE_SIZE) high aspect ratio (panorama) + // Bitmap requests: + // Step 1: ask for double size. + // Step 2: scale maximum edge down to MAXIMUM_TEXTURE_SIZE. + // + // Here's the step 1: double size. + if ((heightBound > GL11.GL_MAX_TEXTURE_SIZE || widthBound > GL11.GL_MAX_TEXTURE_SIZE) && + targetWidth * targetHeight < maximumPixels / 4 && sampleSize > 1) { + sampleSize >>= 2; + } + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = sampleSize; + opts.inTempStorage = decodeBuffer; + Bitmap b = BitmapFactory.decodeStream(stream, null, opts); + + if (b == null) { + return null; + } + + // Step 2: scale maximum edge down to maximum texture size. + // If Bitmap maximum edge > MAXIMUM_TEXTURE_SIZE, which can happen for panoramas, + // scale to fit in MAXIMUM_TEXTURE_SIZE. + if (b.getWidth() > GL11.GL_MAX_TEXTURE_SIZE || b.getHeight() > GL11.GL_MAX_TEXTURE_SIZE) { + int maxEdge = Math.max(b.getWidth(), b.getHeight()); + b = Bitmap.createScaledBitmap(b, b.getWidth() * GL11.GL_MAX_TEXTURE_SIZE / maxEdge, + b.getHeight() * GL11.GL_MAX_TEXTURE_SIZE / maxEdge, + false); + } + + // Not called often because most modes save image data non-rotated. + if (orientation != 0 && b != null) { + Matrix m = new Matrix(); + m.setRotate(orientation); + b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false); + } + + return b; + } + + public static Optional generateThumbnail(File path, int boundingWidthPx, + int boundingHeightPx) { + final Bitmap bitmap; + + /*if (getAttributes().isRendering()) { + return Storage.getPlaceholderForSession(data.getUri()); + } else {*/ + + FileInputStream stream; + + try { + stream = new FileInputStream(path); + } catch (FileNotFoundException e) { + Log.e(TAG, "### File not found ###:" + path.getPath()); + return Optional.empty(); + } + int width = 1280; + int height = 720; //.getDimensions().getHeight(); + int orientation = 0; + + Point dim = resizeToFill(width, height, orientation, boundingWidthPx, boundingHeightPx); + + // If the orientation is not vertical + if (orientation % 180 != 0) { + int dummy = dim.x; + dim.x = dim.y; + dim.y = dummy; + } + + bitmap = loadImageThumbnailFromStream(stream, width, height, (int)(dim.x * 0.7f), + (int)(dim.y * 0.7), 0, MAX_PEEK_BITMAP_PIXELS); + + return Optional.ofNullable(bitmap); + //} + } + + /** + * Calculates a new dimension to fill the bound with the original aspect + * ratio preserved. + * + * @param imageWidth The original width. + * @param imageHeight The original height. + * @param imageRotation The clockwise rotation in degrees of the image which + * the original dimension comes from. + * @param boundWidth The width of the bound. + * @param boundHeight The height of the bound. + * @returns The final width/height stored in Point.x/Point.y to fill the + * bounds and preserve image aspect ratio. + */ + public static Point resizeToFill(int imageWidth, int imageHeight, int imageRotation, + int boundWidth, int boundHeight) { + if (imageRotation % 180 != 0) { + // Swap width and height. + int savedWidth = imageWidth; + imageWidth = imageHeight; + imageHeight = savedWidth; + } + + Point p = new Point(); + p.x = boundWidth; + p.y = boundHeight; + + // In some cases like automated testing, image height/width may not be + // loaded, to avoid divide by zero fall back to provided bounds. + if (imageWidth != 0 && imageHeight != 0) { + if (imageWidth * boundHeight > boundWidth * imageHeight) { + p.y = imageHeight * p.x / imageWidth; + } else { + p.x = imageWidth * p.y / imageHeight; + } + } else { + Log.w(TAG, "zero width/height, falling back to bounds (w|h|bw|bh):" + imageWidth + "|" + + imageHeight + "|" + boundWidth + "|" + boundHeight); + } + + return p; + } + + /** + * Rotates and/or mirrors the bitmap. If a new bitmap is created, the + * original bitmap is recycled. + */ + public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) { + if ((degrees != 0 || mirror) && b != null) { + Matrix m = new Matrix(); + // Mirror first. + // horizontal flip + rotation = -rotation + horizontal flip + if (mirror) { + m.postScale(-1, 1); + degrees = (degrees + 360) % 360; + if (degrees == 0 || degrees == 180) { + m.postTranslate(b.getWidth(), 0); + } else if (degrees == 90 || degrees == 270) { + m.postTranslate(b.getHeight(), 0); + } else { + throw new IllegalArgumentException("Invalid degrees=" + degrees); + } + } + if (degrees != 0) { + // clockwise + m.postRotate(degrees, (float)b.getWidth() / 2, (float)b.getHeight() / 2); + } + + try { + Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true); + if (b != b2) { + b.recycle(); + b = b2; + } + } catch (OutOfMemoryError ex) { + // We have no memory to rotate. Return the original bitmap. + } + } + return b; + } + + public static Optional getVideoThumbnail(ContentResolver mContentResolver, Uri uri) { + Bitmap bitmap = null; + ParcelFileDescriptor mVideoFileDescriptor; + + try { + mVideoFileDescriptor = mContentResolver.openFileDescriptor(uri, "r"); + bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(), + 720); + } catch (java.io.FileNotFoundException ex) { + // invalid uri + Log.e(TAG, ex.toString()); + } + + if (bitmap != null) { + // MetadataRetriever already rotates the thumbnail. We should rotate + // it to match the UI orientation (and mirror if it is front-facing camera). + bitmap = rotateAndMirror(bitmap, 0, false); + } + return Optional.ofNullable(bitmap); + } + + public static Intent getVideoPlayerIntent(Uri uri) { + return new Intent(Intent.ACTION_VIEW).setDataAndType(uri, "video/*"); + } + + public static void playVideo(Activity activity, Uri uri, String title) { + try { + Intent intent = getVideoPlayerIntent(uri) + .putExtra(Intent.EXTRA_TITLE, title) + .putExtra(KEY_TREAT_UP_AS_BACK, true); + activity.startActivity(intent); + + } catch (ActivityNotFoundException e) { + Log.e(TAG, "cant play video"); + } + } + + public static String getRealPathFromURI(Context context, Uri contentUri) { + Cursor cursor = null; + int column_index; + try { + String[] proj = {MediaStore.Images.Media.DATA, MediaStore.Video.Media.DATA}; + cursor = context.getContentResolver().query(contentUri, proj, null, null, null); + + if (getMimeTypeFromURI(context, contentUri).compareTo("video/mp4") == 0) { + column_index = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA); + cursor.moveToFirst(); + + } else { + column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + } + + return cursor.getString(column_index); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + public static String getMimeTypeFromURI(Context context, Uri uri) { + ContentResolver cR = context.getContentResolver(); + String type = cR.getType(uri); + return type; + } } diff --git a/camera/MultiCameraApplication/res/drawable-v24/ic_launcher_foreground.xml b/camera/MultiCameraApplication/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 1f6bb29..0000000 --- a/camera/MultiCameraApplication/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/camera/MultiCameraApplication/res/drawable/bg_text_on_preview.xml b/camera/MultiCameraApplication/res/drawable/bg_text_on_preview.xml new file mode 100644 index 0000000..cdc2b04 --- /dev/null +++ b/camera/MultiCameraApplication/res/drawable/bg_text_on_preview.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/camera/MultiCameraApplication/res/drawable/ic_back_normal.png b/camera/MultiCameraApplication/res/drawable/ic_back_normal.png new file mode 100644 index 0000000..d326323 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_back_normal.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_capture_camera_disabled.png b/camera/MultiCameraApplication/res/drawable/ic_capture_camera_disabled.png new file mode 100644 index 0000000..af6833f Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_capture_camera_disabled.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_capture_camera_normal.png b/camera/MultiCameraApplication/res/drawable/ic_capture_camera_normal.png new file mode 100644 index 0000000..21bd151 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_capture_camera_normal.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_capture_video.png b/camera/MultiCameraApplication/res/drawable/ic_capture_video.png new file mode 100644 index 0000000..6dba337 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_capture_video.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_capture_video_disabled.png b/camera/MultiCameraApplication/res/drawable/ic_capture_video_disabled.png new file mode 100644 index 0000000..299c01c Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_capture_video_disabled.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_confirm.png b/camera/MultiCameraApplication/res/drawable/ic_confirm.png new file mode 100644 index 0000000..6894bd2 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_confirm.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_control_play.png b/camera/MultiCameraApplication/res/drawable/ic_control_play.png new file mode 100644 index 0000000..2de5b4f Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_control_play.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_exitscreeen.png b/camera/MultiCameraApplication/res/drawable/ic_exitscreeen.png new file mode 100644 index 0000000..cbbdca5 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_exitscreeen.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_full_screen.png b/camera/MultiCameraApplication/res/drawable/ic_full_screen.png new file mode 100644 index 0000000..8cff8ff Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_full_screen.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_launcher_foreground.xml b/camera/MultiCameraApplication/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index f8e8dca..0000000 --- a/camera/MultiCameraApplication/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/camera/MultiCameraApplication/res/drawable/ic_menu_cancel_holo_light.png b/camera/MultiCameraApplication/res/drawable/ic_menu_cancel_holo_light.png new file mode 100644 index 0000000..335deaa Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_menu_cancel_holo_light.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_menu_trash.xml b/camera/MultiCameraApplication/res/drawable/ic_menu_trash.xml new file mode 100644 index 0000000..cb94342 --- /dev/null +++ b/camera/MultiCameraApplication/res/drawable/ic_menu_trash.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/camera/MultiCameraApplication/res/drawable/ic_record.png b/camera/MultiCameraApplication/res/drawable/ic_record.png new file mode 100644 index 0000000..a36626a Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_record.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_recording_indicator.png b/camera/MultiCameraApplication/res/drawable/ic_recording_indicator.png new file mode 100755 index 0000000..aa8781d Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_recording_indicator.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_settings_black_24dp.xml b/camera/MultiCameraApplication/res/drawable/ic_settings_black_24dp.xml index 7f2f06c..314de0f 100644 --- a/camera/MultiCameraApplication/res/drawable/ic_settings_black_24dp.xml +++ b/camera/MultiCameraApplication/res/drawable/ic_settings_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="35dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/camera/MultiCameraApplication/res/drawable/ic_settings_normal.png b/camera/MultiCameraApplication/res/drawable/ic_settings_normal.png new file mode 100644 index 0000000..61f70b4 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_settings_normal.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_settings_normal_disabled.png b/camera/MultiCameraApplication/res/drawable/ic_settings_normal_disabled.png new file mode 100644 index 0000000..aff3b03 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_settings_normal_disabled.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_stop_normal.png b/camera/MultiCameraApplication/res/drawable/ic_stop_normal.png new file mode 100644 index 0000000..299aca3 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_stop_normal.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_trash_disabled.png b/camera/MultiCameraApplication/res/drawable/ic_trash_disabled.png new file mode 100644 index 0000000..1f1a215 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_trash_disabled.png differ diff --git a/camera/MultiCameraApplication/res/drawable/ic_trash_normal.png b/camera/MultiCameraApplication/res/drawable/ic_trash_normal.png new file mode 100644 index 0000000..e0a9ff6 Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_trash_normal.png differ diff --git a/camera/MultiCameraApplication/res/drawable/photo_selector.xml b/camera/MultiCameraApplication/res/drawable/photo_selector.xml new file mode 100644 index 0000000..80ece7d --- /dev/null +++ b/camera/MultiCameraApplication/res/drawable/photo_selector.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/res/drawable/transparent_button_background.xml b/camera/MultiCameraApplication/res/drawable/transparent_button_background.xml new file mode 100644 index 0000000..fa35789 --- /dev/null +++ b/camera/MultiCameraApplication/res/drawable/transparent_button_background.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/camera/MultiCameraApplication/res/drawable/video_selector.xml b/camera/MultiCameraApplication/res/drawable/video_selector.xml new file mode 100644 index 0000000..4daeb16 --- /dev/null +++ b/camera/MultiCameraApplication/res/drawable/video_selector.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/res/layout/activity_itscameraintents.xml b/camera/MultiCameraApplication/res/layout/activity_itscameraintents.xml new file mode 100644 index 0000000..3389cdc --- /dev/null +++ b/camera/MultiCameraApplication/res/layout/activity_itscameraintents.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/res/layout/activity_main.xml b/camera/MultiCameraApplication/res/layout/activity_multiview.xml similarity index 61% rename from camera/MultiCameraApplication/res/layout/activity_main.xml rename to camera/MultiCameraApplication/res/layout/activity_multiview.xml index 33bb882..7651273 100644 --- a/camera/MultiCameraApplication/res/layout/activity_main.xml +++ b/camera/MultiCameraApplication/res/layout/activity_multiview.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + tools:context=".MultiViewActivity"> - + + + + + + + + + - \ No newline at end of file diff --git a/camera/MultiCameraApplication/res/layout/botmleftcam.xml b/camera/MultiCameraApplication/res/layout/botmleftcam.xml new file mode 100644 index 0000000..d89271d --- /dev/null +++ b/camera/MultiCameraApplication/res/layout/botmleftcam.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/res/layout/botmrightcam.xml b/camera/MultiCameraApplication/res/layout/botmrightcam.xml new file mode 100644 index 0000000..8b4a7bc --- /dev/null +++ b/camera/MultiCameraApplication/res/layout/botmrightcam.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camera/MultiCameraApplication/res/layout/content_main.xml b/camera/MultiCameraApplication/res/layout/content_main.xml index f11fad7..b4c895a 100644 --- a/camera/MultiCameraApplication/res/layout/content_main.xml +++ b/camera/MultiCameraApplication/res/layout/content_main.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" - tools:context=".MainActivity" + tools:context=".MultiViewActivity" tools:showIn="@layout/activity_main"> + android:baselineAligned="false" + android:orientation="horizontal"> - + - + - - -