diff --git a/camera/.clang-format b/camera/.clang-format
new file mode 100644
index 0000000..a6eb858
--- /dev/null
+++ b/camera/.clang-format
@@ -0,0 +1,151 @@
+---
+Language: Java
+BasedOnStyle: Google
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: true
+DerivePointerAlignment: true
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^'
+ Priority: 2
+ - Regex: '^<.*\.h>'
+ Priority: 1
+ - Regex: '^<.*'
+ Priority: 2
+ - Regex: '.*'
+ Priority: 3
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Left
+RawStringFormats:
+ - Language: Cpp
+ Delimiters:
+ - cc
+ - CC
+ - cpp
+ - Cpp
+ - CPP
+ - 'c++'
+ - 'C++'
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ - Language: TextProto
+ Delimiters:
+ - pb
+ - PB
+ - proto
+ - PROTO
+ EnclosingFunctions:
+ - EqualsProto
+ - EquivToProto
+ - PARSE_PARTIAL_TEXT_PROTO
+ - PARSE_TEST_PROTO
+ - PARSE_TEXT_PROTO
+ - ParseTextOrDie
+ - ParseTextProtoOrDie
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Auto
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseTab: Never
+...
+
diff --git a/camera/MultiCameraApplication/Android.mk b/camera/MultiCameraApplication/Android.mk
index ebd5fc5..6fc2b5b 100644
--- a/camera/MultiCameraApplication/Android.mk
+++ b/camera/MultiCameraApplication/Android.mk
@@ -1,18 +1,37 @@
LOCAL_PATH:= $(call my-dir)
-
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := optional
-LOCAL_CERTIFICATE := platform
-LOCAL_SDK_VERSION := current
+LOCAL_PACKAGE_NAME := MultiCameraApp
+LOCAL_MODULE_TAGS := optional
+LOCAL_DEX_PREOPT := false
+LOCAL_CERTIFICATE := platform
+LOCAL_SDK_VERSION := current
LOCAL_MIN_SDK_VERSION := 27
-LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, java)
-LOCAL_DEX_PREOPT := false
-LOCAL_PACKAGE_NAME := MultiCameraApp
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
-include $(BUILD_PACKAGE)
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+
+LOCAL_AIDL_INCLUDES := \
+ frameworks/native/aidl/gui
-include $(call all-makefiles-under, $(LOCAL_PATH))
+#LOCAL_PROGUARD_FLAG_FILES := ../../../frameworks/support/design/proguard-rules.pro
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_STATIC_JAVA_LIBRARIES = \
+ androidx-constraintlayout_constraintlayout-solver
+
+LOCAL_PROPRIETARY_MODULE := true
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ androidx-constraintlayout_constraintlayout \
+ androidx.preference_preference \
+ androidx.cardview_cardview \
+ com.google.android.material_material \
+ androidx.legacy_legacy-support-v13 \
+ androidx.legacy_legacy-support-v4 \
+ androidx.appcompat_appcompat
+
+include $(BUILD_PACKAGE)
diff --git a/camera/MultiCameraApplication/AndroidManifest.xml b/camera/MultiCameraApplication/AndroidManifest.xml
index 862e909..6ee8cf1 100644
--- a/camera/MultiCameraApplication/AndroidManifest.xml
+++ b/camera/MultiCameraApplication/AndroidManifest.xml
@@ -1,27 +1,62 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java b/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java
new file mode 100644
index 0000000..e99dac6
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/AutoFitTextureView.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 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.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+/**
+ * A {@link TextureView} that can be adjusted to a specified aspect ratio.
+ */
+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);
+ }
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based
+ * 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
+ */
+ public void setAspectRatio(int width, int height) {
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Size cannot be negative.");
+ }
+ mRatioWidth = width;
+ mRatioHeight = height;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (0 == mRatioWidth || 0 == mRatioHeight) {
+ setMeasuredDimension(width, height);
+ } else {
+ if (width < height * mRatioWidth / mRatioHeight) {
+ setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
+ } else {
+ setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
+ }
+ }
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/CameraBase.java b/camera/MultiCameraApplication/java/com/intel/multicamera/CameraBase.java
new file mode 100644
index 0000000..d0d2b48
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/CameraBase.java
@@ -0,0 +1,769 @@
+/*
+ * 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.app.Activity;
+import android.app.Dialog;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Picture;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.*;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.CamcorderProfile;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Build;
+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;
+import android.util.SparseIntArray;
+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.preference.PreferenceManager;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+
+public class CameraBase {
+ private Activity mActivity;
+ private String TAG;
+ /**
+ * An {@link AutoFitTextureView} for camera preview.
+ */
+ private AutoFitTextureView textureView;
+ private ImageButton FullScrn, SettingsView, takePictureButton, TakeVideoButton;
+
+ private String cameraId;
+ private CameraDevice mCameraDevice;
+ private CameraCaptureSession cameraCaptureSessions;
+ private CaptureRequest.Builder captureRequestBuilder;
+ private Size previewSize;
+ private ImageReader imageReader;
+ private Handler mBackgroundHandler;
+ private HandlerThread mBackgroundThread;
+ private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+ private SharedPreferences settings;
+ private SurfaceTexture mSurfaceTexture;
+ private String Capture_Key, Video_key, SettingsKey;
+
+ private CameraBase mCameraBase;
+ private static final String SIZE_HD = "HD 720p";
+
+ /**
+ * Whether the app is recording video now
+ */
+ private boolean mIsRecordingVideo;
+ private MultiCamera ic_camera;
+
+ private PhotoPreview mPhoto;
+ private VideoRecord mRecord;
+ /**
+ * 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);
+ }
+
+ static final Size SIZE_720P = new Size(1280, 720);
+ static final Size SIZE_480P = new Size(640, 480);
+
+ private RoundedThumbnailView mRoundedThumbnailView;
+ FrameLayout roundedThumbnailViewControlLayout;
+
+ private String[] ImageFileDetails;
+
+ public CameraBase(Activity activity, AutoFitTextureView mtextureView, ImageButton[] Button,
+ TextView RecordingTimeView, String[] data,
+ RoundedThumbnailView roundedThumbnailView) {
+ this.mActivity = activity;
+ this.textureView = mtextureView;
+ if (Button != null) {
+ this.ClickListeners(Button[0], Button[1], Button[2], Button[4], Button[5]);
+ 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];
+
+ ic_camera = MultiCamera.getInstance();
+
+ mPhoto = new PhotoPreview(activity, roundedThumbnailView, cameraId);
+ mRecord = new VideoRecord(this, Video_key,cameraId, mtextureView, mActivity,
+ RecordingTimeView, SettingsKey);
+
+ roundedThumbnailViewControlLayout = mActivity.findViewById(R.id.control1);
+ mCameraBase = this;
+ }
+
+ private void ClickListeners(ImageButton PictureButton, ImageButton RecordButton,
+ ImageButton Settings, ImageButton CameraSwitch, ImageButton CameraSplit) {
+ TakePicureOnClicked(PictureButton);
+
+ StartVideoRecording(RecordButton, PictureButton, CameraSwitch, CameraSplit, Settings);
+ CameraSplit(CameraSplit);
+ }
+
+
+ private void TakePicureOnClicked(ImageButton PictureButton) {
+ takePictureButton = PictureButton;
+ if (takePictureButton == null) return;
+
+ takePictureButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (ic_camera.getTopRightCam() != null &&
+ ic_camera.getTopRightCam() == mCameraBase) {
+ ic_camera.setOpenCameraId(1);
+ }
+ if (ic_camera.getBotLeftCam() != null && ic_camera.getBotLeftCam() == mCameraBase) {
+ ic_camera.setOpenCameraId(2);
+ }
+ if (ic_camera.getTopLeftCam() != null && ic_camera.getTopLeftCam() == mCameraBase) {
+ ic_camera.setOpenCameraId(0);
+ }
+ if (ic_camera.getBotRightCam() != null &&
+ ic_camera.getBotRightCam() == mCameraBase) {
+ ic_camera.setOpenCameraId(3);
+ }
+ System.out.println(TAG + "take pic start camera id"+ic_camera.getOpenCameraId());
+
+ MultiViewActivity Mactivity = (MultiViewActivity) mActivity;
+ Mactivity.closeCamera();
+ ic_camera.setIsCameraOrSurveillance(0);
+ Intent intent = new Intent(mActivity, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mActivity.startActivity(intent);
+ mActivity.finish();
+
+ }
+ });
+ }
+
+ private void CameraSplit(ImageButton cameraSplit) {
+ if (cameraSplit == null) return;
+
+ cameraSplit.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Toast.makeText(mActivity, "camera split clicked ", Toast.LENGTH_LONG).show();
+ System.out.println("camera split");
+ MultiCamera ic_camera = MultiCamera.getInstance();
+ MultiViewActivity Mactivity = (MultiViewActivity) mActivity;
+ if (ic_camera.getIsCameraOrSurveillance() == 0) {
+ ic_camera.setIsCameraOrSurveillance(1);
+ Intent intent = new Intent(mActivity, MultiViewActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mActivity.startActivity(intent);
+ mActivity.finish();
+ } else {
+ System.out.println("camera split calling single camera activity");
+ ic_camera.setIsCameraOrSurveillance(1);
+ Intent intent = new Intent(mActivity, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mActivity.startActivity(intent);
+ mActivity.finish();
+ }
+ }
+ });
+ }
+
+ private void StartVideoRecording(ImageButton RecordButton, final ImageButton Capture,
+ ImageButton Switch, final ImageButton Split, final ImageButton Settings) {
+ TakeVideoButton = RecordButton;
+
+ TakeVideoButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ MultiViewActivity MultiActivity = (MultiViewActivity) mActivity;
+
+ if (mIsRecordingVideo) {
+ mIsRecordingVideo = false;
+
+ mRecord.stopRecordingVideo();
+ mRecord.showRecordingUI(false);
+ mPhoto.showVideoThumbnail();
+
+ MultiActivity.enterFullScreen(view);
+ Capture.setVisibility(View.VISIBLE);
+ TakeVideoButton.setImageResource(R.drawable.ic_capture_video);
+ Split.setVisibility(View.VISIBLE);
+ Settings.setVisibility(View.VISIBLE);
+ } else {
+
+ mIsRecordingVideo =true;
+ mRecord.startRecordingVideo();
+ mRecord.showRecordingUI(true);
+ MultiActivity.enterFullScreen(view);
+ Settings.setVisibility(View.GONE);
+ Capture.setVisibility(View.GONE);
+ TakeVideoButton.setImageResource(R.drawable.ic_stop_normal);
+ Split.setVisibility(View.GONE);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Recording failed");
+ }
+ }
+ });
+ }
+
+ TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ // open your camera here
+ mSurfaceTexture = surface;
+ try {
+ TimeUnit.MILLISECONDS.sleep(100);
+ } catch (Exception e) {
+
+ }
+ 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
+
+ adjustAspectRatio(previewSize.getWidth(), previewSize.getHeight());
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+ };
+
+ private void openCamera(int width, int height) {
+ CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+ StreamConfigurationMap map =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (map == null) return;
+
+ String Key = GetChnagedPrefKey();
+ if (Key == null)
+ return;
+
+ previewSize = getSelectedDimension(Key);
+
+ configureTransform(width, height);
+
+ mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+
+ manager.openCamera(cameraId, stateCallback, null);
+
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ // This is called when the camera is open
+ Log.i(TAG, "Camera opened successfully with id " + camera.getId());
+ mCameraDevice = camera;
+ mRecord.setCameraDevice(mCameraDevice);
+
+ createCameraPreview();
+ MultiViewActivity.updateStorageSpace(null);
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ Log.v(TAG, "onDisconnected");
+ mCameraDevice = camera;
+ closeCamera();
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ Log.v(TAG, "onError");
+ mCameraDevice = camera;
+ closeCamera();
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ Log.v(TAG, "onClose");
+ mCameraDevice = camera;
+ super.onClosed(camera);
+ previewSize = SIZE_720P;
+ configureTransform(textureView.getWidth(), textureView.getHeight());
+ SurfaceUtil.clear(mSurfaceTexture);
+
+ ResetResolutionSettings();
+ }
+ };
+
+ private void ResetResolutionSettings() {
+ SharedPreferences.Editor edit = settings.edit();
+ edit.remove(Capture_Key);
+ edit.remove(Video_key);
+ edit.apply();
+ }
+
+ 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.v(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();
+ 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);
+ }
+
+ /**
+ * Retrieve a setting's value as a String, manually specifiying
+ * a default value.
+ */
+ private 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;
+ }
+ }
+
+ private 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;
+ }
+
+ private Size getSelectedDimension(String Key) {
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+
+ Size mDimensions = SIZE_720P;
+
+ if (Key.compareTo(Video_key) == 0) {
+ CamcorderProfile mProfile;
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+
+ String videoQuality = settings.getString(Video_key, SIZE_HD);
+
+ int quality = SettingsPrefUtil.getFromSetting(videoQuality);
+
+ mProfile = CamcorderProfile.get(0, quality);
+
+ mDimensions = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
+
+ } else {
+ mDimensions = SettingsPrefUtil.sizeFromSettingString(
+ settings.getString(Capture_Key, "1280x720"));
+ }
+
+ return mDimensions;
+ }
+
+ public void createCameraPreview() {
+ try {
+ mRecord.closePreviewSession();
+ SurfaceTexture texture = textureView.getSurfaceTexture();
+ if (texture == null) return;
+
+ Surface surface = new Surface(texture);
+
+ String Key = GetChnagedPrefKey();
+ if (Key == null)
+ return;
+
+ previewSize = getSelectedDimension(Key);
+ if (previewSize == null)
+ return;
+
+ if (previewSize.getWidth() == 640 || previewSize.getWidth() == 320) {
+ previewSize = SIZE_480P;
+
+ } else if (previewSize.getWidth() == 1280 || previewSize.getWidth() == 1920) {
+ previewSize = SIZE_720P;
+ }
+
+ Log.i(TAG, "Previewing with " + Key + " " + previewSize.getWidth() + " x " +
+ previewSize.getHeight());
+
+ texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+
+ adjustAspectRatio(previewSize.getWidth(), previewSize.getHeight());
+
+ captureRequestBuilder =
+ mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ captureRequestBuilder.addTarget(surface);
+
+ mCameraDevice.createCaptureSession(
+ Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ // The camera is already closed
+ if (null == mCameraDevice) {
+ return;
+ }
+ // When the session is ready, we start displaying the preview.
+ cameraCaptureSessions = cameraCaptureSession;
+ updatePreview();
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
+ closeCamera();
+ Toast.makeText(mActivity, " Preview Configuration change",
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ }, null);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void closeCamera() {
+ try {
+ if (mIsRecordingVideo) {
+ mRecord.stopRecordingVideo();
+ }
+ mRecord.closePreviewSession();
+ try {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (null != mCameraDevice) {
+ //mCameraDevice.wait(200);
+ mCameraDevice.close();
+ mCameraDevice = null;
+ }
+ }
+ }).start();
+ } catch (Exception e) {
+ System.out.println(TAG +" camera close exception");
+ }
+
+ if (null != imageReader) {
+ imageReader.close();
+ imageReader = null;
+ }
+
+ mRecord.releaseMedia();
+
+ } catch (Exception e) {
+ System.out.println("close camera exception ");
+ }
+ }
+
+ /**
+ * Sets the TextureView transform to preserve the aspect ratio of the video.
+ */
+ private void adjustAspectRatio(int videoWidth, int videoHeight) {
+ float megaPixels = previewSize.getWidth() * previewSize.getHeight() / 1000000f;
+ int numerator = SettingsPrefUtil.aspectRatioNumerator(previewSize);
+ int denominator = SettingsPrefUtil.aspectRatioDenominator(previewSize);
+ String AspectRatio =
+ mActivity.getString(R.string.metadata_dimensions_format, previewSize.getWidth(),
+ previewSize.getHeight(), megaPixels, numerator, denominator);
+ Log.i(TAG, "AspectRatio is " + AspectRatio);
+
+ if (videoWidth >= 1280 || videoHeight >= 720) {
+ configureTransform(textureView.getWidth(), textureView.getHeight());
+ return;
+ }
+
+ int viewWidth = textureView.getWidth();
+ int viewHeight = textureView.getHeight();
+ double aspectRatio = (double)videoHeight / videoWidth;
+
+ int newWidth, newHeight;
+ if (viewHeight > (int)(viewWidth * aspectRatio)) {
+ // limited by narrow width; restrict height
+ newWidth = viewWidth;
+ newHeight = (int)(viewWidth * aspectRatio);
+ } else {
+ // limited by short height; restrict width
+ newWidth = (int)(viewHeight / aspectRatio);
+ newHeight = viewHeight;
+ }
+ int xoff = (viewWidth - newWidth) / 2;
+ int yoff = (viewHeight - newHeight) / 2;
+ Log.v(TAG, "video=" + videoWidth + "x" + videoHeight + " view=" + viewWidth + "x" +
+ viewHeight + " newView=" + newWidth + "x" + newHeight + " off=" + xoff +
+ "," + yoff);
+
+ Matrix txform = new Matrix();
+ textureView.getTransform(txform);
+ txform.setScale((float)newWidth / viewWidth, (float)newHeight / viewHeight);
+ // txform.postRotate(10); // just for fun
+ txform.postTranslate(xoff, yoff);
+ textureView.setTransform(txform);
+ }
+
+ private void updatePreview() {
+ if (null == mCameraDevice) {
+ Log.e(TAG, "updatePreview error");
+ }
+ 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;
+ }
+
+ public void takePicture() {
+ if (null == mCameraDevice) {
+ Log.e(TAG, "cameraDevice is null");
+ return;
+ }
+
+ try {
+ final Size imageDimension = getSelectedDimension(Capture_Key);
+ if (imageDimension == null) {
+ Log.i(TAG, "Fail to get Dimension");
+ return;
+ }
+
+ Log.i(TAG, "Still Capture imageDimension " + imageDimension.getWidth() + " x " +
+ imageDimension.getHeight());
+
+ ImageReader reader = ImageReader.newInstance(
+ imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.JPEG, 1);
+ List outputSurfaces = new ArrayList<>(2);
+
+ outputSurfaces.add(reader.getSurface());
+
+ captureRequestBuilder =
+ mCameraDevice.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));
+
+ ImageFileDetails = Utils.generateFileDetails(Utils.MEDIA_TYPE_IMAGE);
+ if (ImageFileDetails == null || ImageFileDetails.length < 5) {
+ Log.e(TAG, "takePicture Invalid file details");
+ return;
+ }
+ String mPictureFilename = ImageFileDetails[3];
+
+ final File ImageFile = 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);
+
+ 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(ImageFile);
+ 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);
+
+ saveImage(imageDimension, ImageFile);
+
+ mPhoto.showImageThumbnail(ImageFile);
+
+ createCameraPreview();
+ }
+ };
+ mCameraDevice.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();
+ }
+ }
+
+ private void saveImage(Size imageDimension, File ImageFile) {
+ ContentValues mCurrentPictureValues;
+ Uri uri;
+
+ mCurrentPictureValues = Utils.getContentValues(
+ Utils.MEDIA_TYPE_IMAGE, ImageFileDetails, imageDimension.getWidth(),
+ imageDimension.getHeight(), 0, ImageFile.length());
+
+ uri = Utils.broadcastNewPicture(mActivity.getApplicationContext(), mCurrentPictureValues);
+
+ ic_camera.setCurrentUri(uri);
+
+ ic_camera.setCurrentFileInfo(mCurrentPictureValues);
+
+ Log.i(TAG, "Image saved @ " + ImageFile.getAbsolutePath());
+ }
+
+ private void showDetailsDialog(ContentValues info) {
+ Optional details = Utils.getMediaDetails(mActivity, info);
+ if (!details.isPresent()) {
+ return;
+ }
+ Dialog detailDialog = DetailsDialog.create(mActivity, details.get());
+ detailDialog.show();
+ }
+
+ public PhotoPreview getmPhoto() {
+ return mPhoto;
+ }
+
+ public VideoRecord getmRecord() {
+ return mRecord;
+ }
+
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCamIntents.java b/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCamIntents.java
new file mode 100644
index 0000000..e888cbb
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/CtsCamIntents.java
@@ -0,0 +1,850 @@
+/*
+ * 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.app.Activity;
+import android.content.*;
+import android.graphics.Bitmap;
+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.Bundle;
+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;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+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.preference.PreferenceManager;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+public class CtsCamIntents {
+ Activity mActivity;
+ private static final String TAG = "CtsCamIntents";
+ private CamcorderProfile mProfile;
+ /**
+ * An {@link AutoFitTextureView} for camera preview.
+ */
+ private AutoFitTextureView textureView;
+ private ImageView takePictureButton, TakeVideoButton;
+
+ private MediaRecorder mMediaRecorder;
+ private String cameraId;
+ protected CameraDevice cameraDevice;
+ protected CameraCaptureSession cameraCaptureSessions;
+ protected CaptureRequest.Builder captureRequestBuilder;
+ private Size 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;
+ 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;
+
+ // 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;
+
+ private boolean mIsVideoCaptureIntent, mIsImageCaptureIntent;
+ /**
+ * 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);
+ }
+
+ private String[] VideofileDetails;
+
+ public CtsCamIntents(Activity activity, AutoFitTextureView mtextureView,
+ ImageView PictureButton, ImageView RecordButton,
+ TextView RecordingTimeView) {
+ Log.e(TAG, "constructor called");
+ this.mActivity = activity;
+
+ mIsRecordingVideo = false;
+
+ mIsVideoCaptureIntent = isVideoCaptureIntent();
+ mIsImageCaptureIntent = isImageCaptureIntent();
+
+ 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() {
+ String action = mActivity.getIntent().getAction();
+ return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
+ }
+
+ public boolean isImageCaptureIntent() {
+ String action = mActivity.getIntent().getAction();
+ return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
+ }
+
+ public void ClickListeners(ImageView PictureButton, ImageView RecordButton) {
+ TakePictureOnClicked(PictureButton);
+
+ StartVideoRecording(RecordButton);
+ }
+
+ private void TakePictureOnClicked(ImageView PictureButton) {
+ takePictureButton = PictureButton;
+ if (takePictureButton == null) return;
+
+ takePictureButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ takePicture();
+
+ if (!mIsImageCaptureIntent) {
+ Utils.broadcastNewPicture(mActivity.getApplicationContext(),
+ mCurrentPictureValues);
+ } else {
+ mIsImageCaptureIntent = false;
+ }
+ }
+ });
+ }
+
+ private void StartVideoRecording(ImageView RecordButton) {
+ TakeVideoButton = RecordButton;
+ TakeVideoButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mIsRecordingVideo == true) {
+ showRecordingUI(mIsRecordingVideo);
+ stopRecordingVideo();
+
+ 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;
+ }
+
+ } else if (mIsRecordingVideo == false) {
+ TakeVideoButton.setImageResource(R.drawable.ic_stop_normal);
+ startRecordingVideo();
+ showRecordingUI(mIsRecordingVideo);
+ takePictureButton.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+
+ 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) {
+ Intent resultIntent = new Intent();
+ int resultCode;
+ if (mIsVideoCaptureIntent) {
+ mIsVideoCaptureIntent = false;
+ resultCode = Activity.RESULT_OK;
+ resultIntent.setData(mCurrentVideoUri);
+ resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ mActivity.setResult(resultCode, resultIntent);
+ } else if (mIsImageCaptureIntent) {
+ mIsImageCaptureIntent = false;
+ resultCode = Activity.RESULT_OK;
+ mActivity.setResult(resultCode);
+ }
+
+ mActivity.finish();
+ }
+
+ TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
+ @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) {
+ }
+ };
+
+ public void openCamera(int width, int height) {
+ CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ cameraId = manager.getCameraIdList()[0];
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+ StreamConfigurationMap map =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (map == null) return;
+
+ int total_psizes = map.getOutputSizes(ImageFormat.JPEG).length;
+
+ 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);
+
+ configureTransform(width, height);
+ startBackgroundThread();
+
+ manager.openCamera(cameraId, stateCallback, null);
+
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ Log.e(TAG, "openCamera");
+ }
+
+ 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");
+
+ 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) {
+ 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 +
+ "previewWidth: " + previewSize.getWidth() +
+ "previewHeight:" + previewSize.getHeight());
+ 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;
+
+ texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+
+ Surface surface = new Surface(texture);
+ mOutPutSurface = surface;
+ 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) {
+ closeCamera();
+ Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT)
+ .show();
+ }
+ }, null);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void closeCamera() {
+ closePreviewSession();
+ if (null != cameraDevice) {
+ cameraDevice.close();
+ cameraDevice = null;
+ }
+ if (null != imageReader) {
+ imageReader.close();
+ imageReader = null;
+ }
+ if (null != mMediaRecorder) {
+ releaseMedia();
+ }
+ closeVideoFileDescriptor();
+ stopBackgroundThread();
+ }
+
+ /**
+ * Starts a background thread and its {@link Handler}.
+ */
+ private void startBackgroundThread() {
+ mBackgroundThread = new HandlerThread("Camera_0");
+ 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 {
+ Log.d(TAG,
+ "Selected imageDimension" + previewSize.getWidth() + previewSize.getHeight());
+
+ ImageReader reader = ImageReader.newInstance(
+ previewSize.getWidth(), previewSize.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, 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 =
+ 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);
+ 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(
+ 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();
+
+ mProfile = CamcorderProfile.get(0, CamcorderProfile.QUALITY_HIGH);
+
+ 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
+ mIsRecordingVideo = true;
+ mRecordingStartTime = SystemClock.uptimeMillis();
+ updateRecordingTime();
+ // Start recording
+ mMediaRecorder.start();
+ }
+ });
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ if (null != mActivity) {
+ Toast.makeText(mActivity, "Failed", Toast.LENGTH_SHORT).show();
+ }
+
+ releaseMedia();
+ }
+ }, mBackgroundHandler);
+ } catch (CameraAccessException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setUpMediaRecorder() throws IOException {
+ if (null == mActivity) {
+ return;
+ }
+ String result = null;
+ ContentResolver mContentResolver = mActivity.getContentResolver();
+
+ Intent intent = mActivity.getIntent();
+ Bundle myExtras = intent.getExtras();
+
+ closeVideoFileDescriptor();
+
+ 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());
+ }
+ }
+ }
+
+ mMediaRecorder = new MediaRecorder();
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+
+ if (mVideoFileDescriptor != null) {
+ mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+ }
+
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+
+ 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 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;
+
+ releaseMedia();
+ mVideoFilename = null;
+ closeVideoFileDescriptor();
+ createCameraPreview();
+ }
+
+ private void closeVideoFileDescriptor() {
+ if (mVideoFileDescriptor != null) {
+ try {
+ mVideoFileDescriptor.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close fd", e);
+ }
+ mVideoFileDescriptor = null;
+ }
+ }
+}
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/DetailsDialog.java b/camera/MultiCameraApplication/java/com/intel/multicamera/DetailsDialog.java
new file mode 100644
index 0000000..f237e36
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/DetailsDialog.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intel.multicamera;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Map.Entry;
+
+/**
+ * Displays details (such as Exif) of a local media item.
+ */
+public class DetailsDialog {
+ /**
+ * Creates a dialog for showing media data.
+ *
+ * @param context the Android context.
+ * @param mediaDetails the media details to display.
+ * @return A dialog that can be made visible to show the media details.
+ */
+ public static Dialog create(Context context, MediaDetails mediaDetails) {
+ ListView detailsList =
+ (ListView)LayoutInflater.from(context).inflate(R.layout.details_list, null, false);
+ detailsList.setAdapter(new DetailsAdapter(context, mediaDetails));
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ return builder.setTitle(R.string.details)
+ .setView(detailsList)
+ .setPositiveButton(R.string.close,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ dialog.dismiss();
+ }
+ })
+ .create();
+ }
+
+ /**
+ * An adapter for feeding a details list view with the contents of a
+ * {@link MediaDetails} instance.
+ */
+ private static class DetailsAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final MediaDetails mMediaDetails;
+ private final ArrayList mItems;
+ private final Locale mDefaultLocale = Locale.getDefault();
+ private final DecimalFormat mDecimalFormat = new DecimalFormat(".####");
+ private int mWidthIndex = -1;
+ private int mHeightIndex = -1;
+
+ public DetailsAdapter(Context context, MediaDetails details) {
+ mContext = context;
+ mMediaDetails = details;
+ mItems = new ArrayList(details.size());
+ setDetails(context, details);
+ }
+
+ private void setDetails(Context context, MediaDetails details) {
+ boolean resolutionIsValid = true;
+ String path = null;
+ for (Entry detail : details) {
+ String value;
+ switch (detail.getKey()) {
+ case MediaDetails.INDEX_SIZE: {
+ value = Formatter.formatFileSize(context, (Long)detail.getValue());
+ break;
+ }
+
+ case MediaDetails.INDEX_WIDTH:
+ mWidthIndex = mItems.size();
+ if (detail.getValue().toString().equalsIgnoreCase("0")) {
+ value = context.getString(R.string.unknown);
+ resolutionIsValid = false;
+ } else {
+ value = toLocalInteger(detail.getValue());
+ }
+ break;
+ case MediaDetails.INDEX_HEIGHT: {
+ mHeightIndex = mItems.size();
+ if (detail.getValue().toString().equalsIgnoreCase("0")) {
+ value = context.getString(R.string.unknown);
+ resolutionIsValid = false;
+ } else {
+ value = toLocalInteger(detail.getValue());
+ }
+ break;
+ }
+ case MediaDetails.INDEX_PATH:
+ // Prepend the new-line as a) paths are usually long, so
+ // the formatting is better and b) an RTL UI will see it
+ // as a separate section and interpret it for what it
+ // is, rather than trying to make it RTL (which messes
+ // up the path).
+ value = "\n" + detail.getValue().toString();
+ path = detail.getValue().toString();
+ break;
+ case MediaDetails.INDEX_ORIENTATION:
+ value = toLocalInteger(detail.getValue());
+ break;
+ default: {
+ Object valueObj = detail.getValue();
+ // This shouldn't happen, log its key to help us
+ // diagnose the problem.
+ if (valueObj == null) {
+ fail("%s's value is Null", getDetailsName(context, detail.getKey()));
+ }
+ value = valueObj.toString();
+ }
+ }
+ int key = detail.getKey();
+ if (details.hasUnit(key)) {
+ value = String.format("%s: %s %s", getDetailsName(context, key), value,
+ context.getString(details.getUnit(key)));
+ } else {
+ value = String.format("%s: %s", getDetailsName(context, key), value);
+ }
+ mItems.add(value);
+ }
+ if (!resolutionIsValid) {
+ resolveResolution(path);
+ }
+ }
+
+ public void resolveResolution(String path) {
+ Bitmap bitmap = BitmapFactory.decodeFile(path);
+ if (bitmap == null) return;
+ onResolutionAvailable(bitmap.getWidth(), bitmap.getHeight());
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mMediaDetails.getDetail(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView tv;
+ if (convertView == null) {
+ tv = (TextView)LayoutInflater.from(mContext).inflate(R.layout.details, parent,
+ false);
+ } else {
+ tv = (TextView)convertView;
+ }
+ tv.setText(mItems.get(position));
+ return tv;
+ }
+
+ public void onResolutionAvailable(int width, int height) {
+ if (width == 0 || height == 0) return;
+ // Update the resolution with the new width and height
+ String widthString =
+ String.format(mDefaultLocale, "%s: %d",
+ getDetailsName(mContext, MediaDetails.INDEX_WIDTH), width);
+ String heightString =
+ String.format(mDefaultLocale, "%s: %d",
+ getDetailsName(mContext, MediaDetails.INDEX_HEIGHT), height);
+ mItems.set(mWidthIndex, String.valueOf(widthString));
+ mItems.set(mHeightIndex, String.valueOf(heightString));
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Converts the given integer (given as String or Integer object) to a
+ * localized String version.
+ */
+ private String toLocalInteger(Object valueObj) {
+ if (valueObj instanceof Integer) {
+ return toLocalNumber((Integer)valueObj);
+ } else {
+ String value = valueObj.toString();
+ try {
+ value = toLocalNumber(Integer.parseInt(value));
+ } catch (NumberFormatException ex) {
+ // Just keep the current "value" if we cannot
+ // parse it as a fallback.
+ }
+ return value;
+ }
+ }
+
+ /** Converts the given integer to a localized String version. */
+ private String toLocalNumber(int n) {
+ return String.format(mDefaultLocale, "%d", n);
+ }
+
+ /** Converts the given double to a localized String version. */
+ private String toLocalNumber(double n) {
+ return mDecimalFormat.format(n);
+ }
+ }
+
+ public static String getDetailsName(Context context, int key) {
+ switch (key) {
+ case MediaDetails.INDEX_TYPE:
+ return context.getString(R.string.type);
+ case MediaDetails.INDEX_TITLE:
+ return context.getString(R.string.title);
+ case MediaDetails.INDEX_DESCRIPTION:
+ return context.getString(R.string.description);
+ case MediaDetails.INDEX_DATETIME:
+ return context.getString(R.string.time);
+ case MediaDetails.INDEX_DATEMODIFIED:
+ return context.getString(R.string.datemodified);
+ case MediaDetails.INDEX_LOCATION:
+ return context.getString(R.string.location);
+ case MediaDetails.INDEX_PATH:
+ return context.getString(R.string.path);
+ case MediaDetails.INDEX_WIDTH:
+ return context.getString(R.string.width);
+ case MediaDetails.INDEX_HEIGHT:
+ return context.getString(R.string.height);
+ case MediaDetails.INDEX_DIMENSIONS:
+ return context.getString(R.string.dimensions);
+ case MediaDetails.INDEX_ORIENTATION:
+ return context.getString(R.string.orientation);
+ case MediaDetails.INDEX_DURATION:
+ return context.getString(R.string.duration);
+ case MediaDetails.INDEX_MIMETYPE:
+ return context.getString(R.string.mimetype);
+ case MediaDetails.INDEX_SIZE:
+ return context.getString(R.string.file_size);
+ case MediaDetails.INDEX_MAKE:
+ return context.getString(R.string.maker);
+ case MediaDetails.INDEX_MODEL:
+ return context.getString(R.string.model);
+ default:
+ return "Unknown key" + key;
+ }
+ }
+
+ /**
+ * Throw an assertion error wit the given message.
+ *
+ * @param message the message, can contain placeholders.
+ * @param args if he message contains placeholders, these values will be
+ * used to fill them.
+ */
+ private static void fail(String message, Object... args) {
+ throw new AssertionError(args.length == 0 ? message : String.format(message, args));
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/FullScreenActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/FullScreenActivity.java
new file mode 100644
index 0000000..76d3970
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/FullScreenActivity.java
@@ -0,0 +1,450 @@
+/*
+ * 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 androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
+import android.hardware.usb.UsbManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static com.intel.multicamera.MultiViewActivity.updateStorageSpace;
+
+public class FullScreenActivity extends AppCompatActivity {
+ private static final String TAG = "FullScreenActivity";
+ private boolean mHasCriticalPermissions;
+
+ public String[] CameraIds;
+ private int numOfCameras;
+ private AutoFitTextureView mCamera_BackView, mCamera_FrontView;
+
+ private ImageButton mCameraSwitch, mCameraPicture, mCameraRecord, mCameraSplit, mSettings;
+ private ImageButton mSettingClose;
+
+ private SettingsPrefUtil Fragment;
+ private long mLastClickTime = 0;
+ MultiCamera mCameraInst;
+ private TextView mRecordingTimeView;
+ private boolean mIsRecordingVideo;
+ private CameraBase mCameraBack;
+ private CameraBase mCameraFront;
+ private RoundedThumbnailView mRoundedThumbnailView;
+ private BroadcastReceiver mUsbReceiver;
+
+ @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_full_screen);
+
+ mIsRecordingVideo = false;
+ mCameraInst = MultiCamera.getInstance();
+ mCameraSwitch = findViewById(R.id.camera_switch);
+ mCameraPicture = findViewById(R.id.Picture);
+ mCameraRecord = findViewById(R.id.Record);
+ mCameraSplit = findViewById(R.id.camera_split_view);
+ mRecordingTimeView = findViewById(R.id.recording_time);
+ mSettings = findViewById(R.id.SettingView);
+ mSettingClose = findViewById(R.id.SettingClose);
+ mRoundedThumbnailView = findViewById(R.id.rounded_thumbnail_view);
+
+ checkPermissions();
+
+ openBackCamera();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+
+ // BroadcastReceiver when insert/remove the device USB plug into/from a USB port
+ mUsbReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ System.out.println("BroadcastReceiver Event");
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
+ System.out.println(TAG+"BroadcastReceiver USB Connected");
+ openBackCamera();
+
+ } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
+ System.out.println(TAG+"BroadcastReceiver USB Disconnected");
+ openBackCamera();
+ }
+ }
+ };
+
+ registerReceiver(mUsbReceiver , filter);
+
+ mCameraSwitch.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
+ return;
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+ MultiCamera ic_camera = MultiCamera.getInstance();
+ if (ic_camera.getWhichCamera() == 0) {
+ openFrontCamera();
+ ic_camera.setWhichCamera(1);
+ Log.i(TAG,"Opened front camera");
+ }
+ else {
+ openBackCamera();
+ ic_camera.setWhichCamera(0);
+ Log.i(TAG,"Opened back camera");
+ }
+
+ }
+ });
+
+ mSettings.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ Bundle bundle;
+ mSettingClose.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.PrefScrnSettings, Fragment)
+ .commit();
+
+ mSettings.setVisibility(View.GONE);
+ mSettingClose.setVisibility(View.VISIBLE);
+ mCameraRecord.setVisibility(View.GONE);
+ mCameraPicture.setVisibility(View.GONE);
+ mCameraSwitch.setVisibility(View.GONE);
+ mCameraSplit.setVisibility(View.GONE);
+ }
+ });
+
+ mSettingClose.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ getFragmentManager().beginTransaction().remove(Fragment).commit();
+
+ v.setVisibility(v.GONE);
+ mSettings.setVisibility(View.VISIBLE);
+
+ mCameraRecord.setVisibility(View.VISIBLE);
+ mCameraPicture.setVisibility(View.VISIBLE);
+ mCameraSwitch.setVisibility(View.VISIBLE);
+ mCameraSplit.setVisibility(View.VISIBLE);
+ }
+ });
+
+ mCameraSplit.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MultiCamera ic_camera = MultiCamera.getInstance();
+ ic_camera.setIsCameraOrSurveillance(1);
+ Intent intent = new Intent(FullScreenActivity.this, MultiViewActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+ });
+
+ mCameraPicture.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
+ return;
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+ if (MultiCamera.getInstance().getWhichCamera() == 1) {
+ if (mCameraFront != null)
+ mCameraFront.takePicture();
+ }
+ else {
+ if (mCameraBack != null)
+ mCameraBack.takePicture();
+ }
+ }
+ });
+
+ mCameraRecord.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+
+ if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
+ return;
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+ try {
+ if (mIsRecordingVideo) {
+ mIsRecordingVideo = false;
+ if (mCameraInst.getWhichCamera() == 0) {
+ mCameraBack.getmRecord().stopRecordingVideo();
+ mCameraBack.getmRecord().showRecordingUI(false);
+ mCameraBack.getmPhoto().showVideoThumbnail();
+ }
+ else {
+ mCameraFront.getmRecord().stopRecordingVideo();
+ mCameraFront.getmRecord().showRecordingUI(false);
+ mCameraFront.getmPhoto().showVideoThumbnail();
+ }
+
+ mCameraSwitch.setVisibility(View.VISIBLE);
+ mCameraPicture.setVisibility(View.VISIBLE);
+ mCameraRecord.setImageResource(R.drawable.ic_capture_video);
+ mCameraSplit.setVisibility(View.VISIBLE);
+ mSettings.setVisibility(View.VISIBLE);
+ } else {
+ mIsRecordingVideo =true;
+ if (mCameraInst.getWhichCamera() == 0) {
+ mCameraBack.getmRecord().startRecordingVideo();
+ mCameraBack.getmRecord().showRecordingUI(true);
+ }
+ else {
+ mCameraFront.getmRecord().startRecordingVideo();
+ mCameraFront.getmRecord().showRecordingUI(true);
+ }
+ mSettings.setVisibility(View.GONE);
+ mCameraSwitch.setVisibility(View.GONE);
+ mCameraPicture.setVisibility(View.GONE);
+ mCameraRecord.setImageResource(R.drawable.ic_stop_normal);
+ mCameraSplit.setVisibility(View.GONE);
+ }
+ } catch (Exception e) {
+ System.out.println(TAG + "exception during record");
+ }
+ }
+ });
+ }
+
+ public void openBackCamera() {
+ closeCamera();
+
+ GetCameraCnt();
+ if (numOfCameras == 0) {
+ Toast.makeText(FullScreenActivity.this, "No Camera Found", Toast.LENGTH_LONG).show();
+ System.out.println(TAG+" no camera found");
+ return;
+ }
+ if (numOfCameras == 1) {
+ findViewById(R.id.camera_switch).setVisibility(View.GONE);
+ findViewById(R.id.camera_split_view).setVisibility(View.GONE);
+ }
+
+ updateStorageSpace(null);
+
+ OpenOnlyBackCamera();
+ }
+
+ public void openFrontCamera() {
+ closeCamera();
+
+ GetCameraCnt();
+ updateStorageSpace(null);
+
+ OpenOnlyFrontCamera();
+ }
+
+ private void OpenOnlyFrontCamera() {
+
+ mCamera_FrontView = findViewById(R.id.textureview);
+ if (mCamera_FrontView == null) {
+ Log.e(TAG, "fail to get surface for front view");
+ return;
+ }
+ if (mCameraFront == null) {
+ Open_FrontCamera();
+ }
+
+ if (mCamera_FrontView.isAvailable()) {
+ mCameraFront.textureListener.onSurfaceTextureAvailable(
+ mCamera_FrontView.getSurfaceTexture(),
+ mCamera_FrontView.getWidth(), mCamera_FrontView.getHeight());
+ } else {
+ mCamera_FrontView.setSurfaceTextureListener(mCameraFront.textureListener);
+ }
+ }
+ private void closeCamera() {
+
+ if (null != mCameraBack) {
+ mCameraBack.closeCamera();
+ mCameraBack = null;
+ }
+
+ if (null != mCameraFront) {
+ mCameraFront.closeCamera();
+ mCameraFront = null;
+ }
+ }
+
+
+ public void GetCameraCnt() {
+ CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
+
+ try {
+ CameraIds = manager.getCameraIdList();
+ numOfCameras = manager.getCameraIdList().length;
+ if (numOfCameras == 0) {
+ Toast.makeText(FullScreenActivity.this, "No camera found closing the application",
+ Toast.LENGTH_LONG).show();
+ }
+ Log.d(TAG, "Get total number of cameras present: " + manager.getCameraIdList().length);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void OpenOnlyBackCamera() {
+ mCamera_BackView = findViewById(R.id.textureview);
+ if (mCamera_BackView == null) {
+ Log.e(TAG, "fail to find surface for back camera");
+ return;
+ }
+ if (mCameraBack == null) {
+ Open_BackCamera();
+ }
+ if (mCamera_BackView.isAvailable()) {
+ mCameraBack.textureListener.onSurfaceTextureAvailable(
+ mCamera_BackView.getSurfaceTexture(), mCamera_BackView.getWidth(),
+ mCamera_BackView.getHeight());
+ } else {
+ mCamera_BackView.setSurfaceTextureListener(mCameraBack.textureListener);
+ }
+ }
+
+ public void Open_BackCamera() {
+ String[] Data = new String[5];
+
+ Data[0] = "BackCamera";
+ Data[1] = CameraIds[0];
+ Data[2] = "capture_list";
+ Data[3] = "video_list";
+ Data[4] = "pref_resolution_1";
+
+ mCameraBack = new CameraBase(this, mCamera_BackView, null, mRecordingTimeView,
+ Data, mRoundedThumbnailView);
+ }
+
+
+ public void Open_FrontCamera() {
+ String[] Data = new String[5];
+
+ Data[0] = "FrontCamera";
+ Data[1] = CameraIds[1];
+ Data[2] = "capture_list_1";
+ Data[3] = "video_list_1";
+ Data[4] = "pref_resolution_1";
+
+ mCameraFront = new CameraBase(this, mCamera_FrontView, null, mRecordingTimeView,
+ Data, mRoundedThumbnailView);
+ }
+ /**
+ * 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() {
+ mHasCriticalPermissions =
+ 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;
+
+ if (!mHasCriticalPermissions) {
+ Intent intent = new Intent(this, PermissionsActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+
+ if (!mHasCriticalPermissions) {
+ Log.v(TAG, "onCreate: Missing critical permissions.");
+ finish();
+ return;
+ }
+
+ }
+
+ @Override
+ protected void onPause() {
+ unregisterReceiver(mUsbReceiver);
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.e(TAG, "onResume");
+ MultiCamera ic_cam = MultiCamera.getInstance();
+ ic_cam.setIsCameraOrSurveillance(0);
+ if (ic_cam.getWhichCamera() == 0) {
+ openBackCamera();
+ } else {
+ openFrontCamera();
+ }
+ }
+}
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 ac00b64..0000000
--- a/camera/MultiCameraApplication/java/com/intel/multicamera/MainActivity.java
+++ /dev/null
@@ -1,794 +0,0 @@
-package com.intel.multicamera;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.ImageFormat;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.RectF;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.media.Image;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Size;
-import android.view.Surface;
-import android.view.TextureView;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import java.util.Arrays;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-public class MainActivity extends Activity {
- private String TAG = "multicamera";
- private Size previewsize,previewsize1,previewsize2,previewsize3;
- private int preview_width =640;
- private int preview_height =480;
- private Semaphore mCameraOpenCloseLock = new Semaphore(1);
- private int vwidth;
- private int vheight;
- private static final int PERMISSIONS_REQUEST_CAMERA = 1;
- private Size jpegSizes[] = null;
-
- private TextureView textureView,textureView1,textureView2,textureView3;
- private CameraDevice cameraDevice,cameraDevice1,cameraDevice2,cameraDevice3;
- private CaptureRequest.Builder previewBuilder,previewBuilder1,previewBuilder2,previewBuilder3;
- private CameraCaptureSession previewSession,previewSession1,previewSession2,previewSession3;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
-
- LinearLayout layout = findViewById(R.id.lay1);
- ViewGroup.LayoutParams params1 = layout.getLayoutParams();
-
- params1.height = displayMetrics.heightPixels;
- params1.width = displayMetrics.widthPixels;
- layout.setLayoutParams(params1);
- Log.d(TAG,"params1.height: "+params1.height+" params1.width: "+ params1.width);
-
-
- vheight = displayMetrics.heightPixels/2;
- vwidth = displayMetrics.widthPixels/2;
-
- Log.d(TAG,"vwidth1: "+vwidth+" vheight1: "+vheight);
-
-
- android.widget.FrameLayout.LayoutParams params = new android.widget.FrameLayout.LayoutParams(vwidth, vheight);
-
- textureView = (TextureView) findViewById(R.id.textureview1);
- textureView1 = (TextureView) findViewById(R.id.textureview2);
- textureView2 = (TextureView) findViewById(R.id.textureview3);
- textureView3 = (TextureView) findViewById(R.id.textureview4);
-
- textureView.setSurfaceTextureListener(surfaceTextureListener);
- textureView1.setSurfaceTextureListener(surfaceTextureListener1);
- textureView2.setSurfaceTextureListener(surfaceTextureListener2);
- textureView3.setSurfaceTextureListener(surfaceTextureListener3);
-
- textureView.setLayoutParams(params);
- textureView1.setLayoutParams(params);
- textureView2.setLayoutParams(params);
- textureView3.setLayoutParams(params);
-
- }
-
-
- @Override
- public void onRequestPermissionsResult (int requestCode, String[] permissions,
- int[] grantResults) {
- if (requestCode == PERMISSIONS_REQUEST_CAMERA) {
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- openCamera();
- } else {
- finish();
- }
- }
-
- }
-
-
-
- private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- Log.d(TAG,"Inside surfaceTextureListener onSurfaceTextureAvailable:");
- openCamera();
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-
- }
- };
-
-
- private TextureView.SurfaceTextureListener surfaceTextureListener1 = new TextureView.SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- Log.d(TAG,"Inside surfaceTextureListener1 onSurfaceTextureAvailable:");
- openCamera1();
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-
- }
- };
-
- private TextureView.SurfaceTextureListener surfaceTextureListener2 = new TextureView.SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- Log.d(TAG,"Inside surfaceTextureListener2 onSurfaceTextureAvailable:");
- openCamera2();
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-
- }
- };
-
- private TextureView.SurfaceTextureListener surfaceTextureListener3 = new TextureView.SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- Log.d(TAG,"Inside surfaceTextureListener3 onSurfaceTextureAvailable:");
- openCamera3();
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-
- }
- };
-
- public void openCamera() {
- CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
-
- try {
- Log.d(TAG,"Inside openCamera() Total Cameras: "+manager.getCameraIdList().length);
- String camerId = manager.getCameraIdList()[0];
- CameraCharacteristics characteristics = manager.getCameraCharacteristics(camerId);
- StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- int total_psizes = map.getOutputSizes(ImageFormat.JPEG).length;
- Log.d(TAG,"openCamera() total_psizes: "+total_psizes);
-
- for (int i=0;i> {
+ @SuppressWarnings("unused") private static final String TAG = "MediaDetails";
+
+ private TreeMap mDetails = new TreeMap();
+ private SparseIntArray mUnits = new SparseIntArray();
+ public static final int INDEX_TYPE = 0;
+ public static final int INDEX_TITLE = 1;
+ public static final int INDEX_DESCRIPTION = 2;
+ public static final int INDEX_DATETIME = 3;
+ public static final int INDEX_DATEMODIFIED = 4;
+ public static final int INDEX_LOCATION = 5;
+ public static final int INDEX_WIDTH = 6;
+ public static final int INDEX_HEIGHT = 7;
+ public static final int INDEX_DIMENSIONS = 8;
+ public static final int INDEX_ORIENTATION = 9;
+ public static final int INDEX_DURATION = 10;
+ public static final int INDEX_MIMETYPE = 11;
+ public static final int INDEX_SIZE = 12;
+
+ // for EXIF
+ public static final int INDEX_MAKE = 100;
+ public static final int INDEX_MODEL = 101;
+
+ // Put this last because it may be long.
+ public static final int INDEX_PATH = 200;
+
+ public void addDetail(int index, Object value) {
+ mDetails.put(index, value);
+ }
+
+ public Object getDetail(int index) {
+ return mDetails.get(index);
+ }
+
+ public int size() {
+ return mDetails.size();
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return mDetails.entrySet().iterator();
+ }
+
+ public void setUnit(int index, int unit) {
+ mUnits.put(index, unit);
+ }
+
+ public boolean hasUnit(int index) {
+ return mUnits.indexOfKey(index) >= 0;
+ }
+
+ public int getUnit(int index) {
+ return mUnits.get(index);
+ }
+
+ /**
+ * Returns a (localized) string for the given duration (in seconds).
+ */
+ public static String formatDuration(final Context context, long seconds) {
+ long h = seconds / 3600;
+ long m = (seconds - h * 3600) / 60;
+ long s = seconds - (h * 3600 + m * 60);
+ String durationValue;
+ if (h == 0) {
+ durationValue = String.format(context.getString(R.string.details_ms), m, s);
+ } else {
+ durationValue = String.format(context.getString(R.string.details_hms), h, m, s);
+ }
+ return durationValue;
+ }
+
+ public static String getDimentions(final Context context, int INDEX_WIDTH, int INDEX_HEIGHT) {
+ float megaPixels = INDEX_HEIGHT * INDEX_WIDTH / 1000000f;
+ Size size = new Size(INDEX_WIDTH, INDEX_HEIGHT);
+ int numerator = SettingsPrefUtil.aspectRatioNumerator(size);
+ int denominator = SettingsPrefUtil.aspectRatioDenominator(size);
+
+ return context.getString(R.string.metadata_dimensions_format, INDEX_WIDTH, INDEX_HEIGHT,
+ megaPixels, numerator, denominator);
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/MultiCamera.java b/camera/MultiCameraApplication/java/com/intel/multicamera/MultiCamera.java
new file mode 100644
index 0000000..68a6ae8
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/MultiCamera.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ContentValues;
+import android.graphics.Camera;
+import android.net.Uri;
+
+public class MultiCamera {
+ private static MultiCamera ic_instance = null;
+ private CameraBase mTopRightCam;
+ private CameraBase mBotmLeftCam;
+ private CameraBase mBotmRightCam;
+ private CameraBase mTopLeftCam;
+
+ private int mOpenCameraId;
+ private Uri mCurrentUri;
+ private ContentValues mCurrentFileInfo;
+
+ MultiCamera() {
+ mWhichCamera = 0;
+ mIsCameraOrSurveillance = 0;
+ mOpenCameraId = -1;
+ }
+ public static MultiCamera getInstance() {
+ if (ic_instance == null) {
+ ic_instance = new MultiCamera();
+ }
+
+ return ic_instance;
+ }
+
+ private int mWhichCamera;
+
+ private int mIsCameraOrSurveillance;
+
+ public int getWhichCamera() {
+ return mWhichCamera;
+ }
+
+ public void setWhichCamera(int whichCamera) {
+ mWhichCamera = whichCamera;
+ }
+
+ public int getIsCameraOrSurveillance() {
+ return mIsCameraOrSurveillance;
+ }
+
+ public void setIsCameraOrSurveillance(int isCameraOrSurveillance) {
+ mIsCameraOrSurveillance = isCameraOrSurveillance;
+ }
+
+ public Uri getCurrentUri() {
+ return mCurrentUri;
+ }
+
+ public void setCurrentUri(Uri currentUri) {
+ this.mCurrentUri = currentUri;
+ }
+
+ public ContentValues getCurrentFileInfo() {
+ return mCurrentFileInfo;
+ }
+
+ public void setCurrentFileInfo(ContentValues currentFileInfo) {
+ this.mCurrentFileInfo = currentFileInfo;
+ }
+
+ public CameraBase getTopRightCam() {
+ return mTopRightCam;
+ }
+
+ public void setTopRightCam(CameraBase topRightCam) {
+ this.mTopRightCam = topRightCam;
+ }
+
+ public CameraBase getBotLeftCam() {
+ return mBotmLeftCam;
+ }
+
+ public void setBotLeftCam(CameraBase botmLeftCam) {
+ this.mBotmLeftCam = botmLeftCam;
+ }
+
+ public CameraBase getBotRightCam() {
+ return mBotmRightCam;
+ }
+
+ public void setBotRightCam(CameraBase botmRightCam) {
+ this.mBotmRightCam = botmRightCam;
+ }
+
+ public CameraBase getTopLeftCam() {
+ return mTopLeftCam;
+ }
+
+ public void setTopLeftCam(CameraBase mTopLeftCam) {
+ this.mTopLeftCam = mTopLeftCam;
+ }
+
+ public int getOpenCameraId() {
+ return mOpenCameraId;
+ }
+
+ public void setOpenCameraId(int openCameraId) {
+ mOpenCameraId = openCameraId;
+ }
+}
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..40ca55e
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/MultiViewActivity.java
@@ -0,0 +1,1304 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
+import android.hardware.usb.UsbManager;
+import android.os.AsyncTask;
+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;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+public class MultiViewActivity extends AppCompatActivity {
+ private static final String TAG = "MultiViewActivity";
+
+
+ /**
+ * An {@link AutoFitTextureView} for camera preview.
+ */
+ private AutoFitTextureView mTopLeftCam_textureView, mTopRightCam_textureView,
+ mBotmLeftCam_textureView, mBotmRightCam_textureView;
+
+ private ImageButton mTopLeftCam_RecordButton, mTopLeftCam_PictureButton, mTopLeftCam_Switch,
+ mTopLeftCam_Split, mTopRightCam_PictureButton, mTopRightCam_RecordButton,
+ mTopRightCam_Switch, mTopRightCam_Split, mBotmLeftCam_PictureButton,
+ mBotmLeftCam_RecordButton, mBotmLeftCam_Switch, mBotmLeftCam_Split,
+ mBotmRightCam_PictureButton, mBotmRightCam_RecordButton, mBotRightCam_Switch,
+ mBotRightCam_Split;
+
+ 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 BroadcastReceiver mUsbReceiver;
+ private int numOfCameras;
+ private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+ private FrameLayout frameView0, frameView1, frameView2, frameView3;
+
+ private SettingsPrefUtil Fragment, Fragment1, Fragment2, Fragment3;
+
+ public String[] CameraIds;
+ private boolean mHasCriticalPermissions;
+
+ private static final Object mStorageSpaceLock = new Object();
+ private static long mStorageSpaceBytes = Utils.LOW_STORAGE_THRESHOLD_BYTES;
+
+ String usbStateChangeAction = "android.hardware.usb.action.USB_STATE";
+
+ 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;
+ private boolean exitRecordScrnFlag;
+ MultiCamera ic_camera;
+ @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");
+
+ ic_camera = MultiCamera.getInstance();
+ checkPermissions();
+ if (!mHasCriticalPermissions) {
+ Log.v(TAG, "onCreate: Missing critical permissions.");
+ finish();
+ return;
+ }
+
+ Settings_Init();
+
+ FullScrn_Init();
+
+ set_FrameVisibilities();
+
+ exitScrn0.setVisibility(View.GONE);
+ exitScrn1.setVisibility(View.GONE);
+ exitScrn2.setVisibility(View.GONE);
+ exitScrn3.setVisibility(View.GONE);
+
+ FullScrn0.setVisibility(View.GONE);
+ FullScrn1.setVisibility(View.GONE);
+ FullScrn2.setVisibility(View.GONE);
+ FullScrn3.setVisibility(View.GONE);
+ startCamera();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+
+ // BroadcastReceiver when insert/remove the device USB plug into/from a USB port
+ mUsbReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ System.out.println("BroadcastReceiver Event");
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
+ System.out.println(TAG + "BroadcastReceiver USB Connected");
+ startCamera();
+
+ } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
+ System.out.println(TAG + "BroadcastReceiver USB Disconnected");
+ startCamera();
+ }
+ }
+ };
+ registerReceiver(mUsbReceiver , filter);
+ }
+
+ void startCamera() {
+ closeCamera();
+ try {
+ TimeUnit.MILLISECONDS.sleep(100);
+ } catch (Exception e) {
+
+ }
+ GetCameraCnt();
+ if (numOfCameras == 0) {
+ Toast.makeText(MultiViewActivity.this, "No Camera Found", Toast.LENGTH_LONG).show();
+ System.out.println(TAG+" no camera found");
+ return;
+ }
+ updateStorageSpace(null);
+ LinearLayout LinLayout1 = findViewById(R.id.TopLayout);
+ LinearLayout LinLayout2 = findViewById(R.id.BtmLayout);
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+ manageTopLeftCam();
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+ manageTopLeftCam();
+ manageTopRightCam();
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+ manageTopLeftCam();
+ manageBotmLeftCam();
+ manageTopRightCam();
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+ manageTopLeftCam();
+ manageTopRightCam();
+ manageBotmLeftCam();
+ manageBotmRightCam();
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+ }
+ private void set_FrameVisibilities() {
+ FrameVisibility = new int[4];
+ frameView0 = findViewById(R.id.control1);
+ frameView1 = findViewById(R.id.control2);
+ frameView2 = findViewById(R.id.control3);
+ frameView3 = findViewById(R.id.control4);
+
+ 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() {
+ mHasCriticalPermissions =
+ 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;
+
+ 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;
+ if (numOfCameras == 0) {
+ Toast.makeText(MultiViewActivity.this, "No camera found closing the application",
+ Toast.LENGTH_LONG).show();
+ }
+ Log.d(TAG, "Get total number of cameras present: " + manager.getCameraIdList().length);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void Open_TopLeftCam() {
+ String[] Data = new String[5];
+ ImageButton[] Buttons = new ImageButton[6];
+
+ mTopLeftCam_textureView = findViewById(R.id.textureview0);
+ if (mTopLeftCam_textureView == null) return;
+
+ mTopLeftCam_textureView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ ic_camera.setOpenCameraId(0);
+ closeCamera();
+ ic_camera.setIsCameraOrSurveillance(0);
+ Intent intent = new Intent(MultiViewActivity.this, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+ });
+ mTopLeftCam_Switch = findViewById(R.id.camera_switch0);
+ mTopLeftCam_Switch.setVisibility(View.VISIBLE);
+ mTopLeftCam_PictureButton = findViewById(R.id.Picture0);
+ mTopLeftCam_RecordButton = findViewById(R.id.Record0);
+ mTopLeftCam_Split = findViewById(R.id.camera_split_view0);
+
+ Buttons[0] = mTopLeftCam_PictureButton;
+ Buttons[1] = mTopLeftCam_RecordButton;
+ Buttons[2] = SettingView0;
+ Buttons[3] = FullScrn0;
+ Buttons[4] = mTopLeftCam_Switch;
+ Buttons[5] = mTopLeftCam_Split;
+
+ mRecordingTimeView = findViewById(R.id.recording_time0);
+
+ 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_view0);
+
+ ic_camera.setTopLeftCam(new CameraBase(this, mTopLeftCam_textureView, Buttons, mRecordingTimeView,
+ Data, roundedThumbnailView));
+ }
+
+ public void Open_TopRightCam() {
+ String[] Data = new String[5];
+ ImageButton[] Buttons = new ImageButton[6];
+ mTopRightCam_textureView = findViewById(R.id.textureview1);
+ if (mTopRightCam_textureView == null) return;
+
+ mTopRightCam_textureView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ic_camera.setOpenCameraId(1);
+ closeCamera();
+ ic_camera.setIsCameraOrSurveillance(0);
+ Intent intent = new Intent(MultiViewActivity.this, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+ });
+
+ mTopRightCam_Switch = findViewById(R.id.camera_switch1);
+ mTopRightCam_Switch.setVisibility(View.VISIBLE);
+ mTopRightCam_PictureButton = findViewById(R.id.Picture1);
+ mTopRightCam_RecordButton = findViewById(R.id.Record1);
+ mTopRightCam_Split = findViewById(R.id.camera_split_view1);
+
+ Buttons[0] = mTopRightCam_PictureButton;
+ Buttons[1] = mTopRightCam_RecordButton;
+ Buttons[2] = SettingView1;
+ Buttons[3] = FullScrn1;
+ Buttons[4] = mTopRightCam_Switch;
+ Buttons[5] = mTopRightCam_Split;
+
+ 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_time1);
+
+ RoundedThumbnailView roundedThumbnailView = findViewById(R.id.rounded_thumbnail_view1);
+
+ ic_camera.setTopRightCam(new CameraBase(this, mTopRightCam_textureView, Buttons,
+ mRecordingTimeView0, Data, roundedThumbnailView));
+ }
+
+ public void Open_BotmLeftCam() {
+ String[] Data = new String[5];
+ ImageButton[] Buttons = new ImageButton[6];
+ mBotmLeftCam_textureView = findViewById(R.id.textureview2);
+ if (mBotmLeftCam_textureView == null) return;
+
+ mBotmLeftCam_textureView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ic_camera.setOpenCameraId(2);
+ closeCamera();
+ Intent intent = new Intent(MultiViewActivity.this, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ ic_camera.setIsCameraOrSurveillance(0);
+
+ }
+ });
+
+ mBotmLeftCam_Switch = findViewById(R.id.camera_switch2);
+
+ mBotmLeftCam_PictureButton = findViewById(R.id.Picture2);
+ mBotmLeftCam_RecordButton = findViewById(R.id.Record2);
+ mBotmLeftCam_Split = findViewById(R.id.camera_split_view2);
+
+ Buttons[0] = mBotmLeftCam_PictureButton;
+ Buttons[1] = mBotmLeftCam_RecordButton;
+ Buttons[2] = SettingView2;
+ Buttons[3] = FullScrn2;
+ Buttons[4] = mBotmLeftCam_Switch;
+ Buttons[5] = mBotmLeftCam_Split;
+
+ 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);
+
+ ic_camera.setBotLeftCam(new CameraBase(this, mBotmLeftCam_textureView, Buttons,
+ mRecordingTimeView1, Data, roundedThumbnailView));
+ }
+
+ public void Open_BotmRightCam() {
+ String[] Data = new String[5];
+ ImageButton[] Buttons = new ImageButton[6];
+ mBotmRightCam_textureView = findViewById(R.id.textureview3);
+ if (mBotmRightCam_textureView == null) return;
+
+ mBotmRightCam_textureView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ic_camera.setOpenCameraId(3);
+ closeCamera();
+ Intent intent = new Intent(MultiViewActivity.this, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ ic_camera.setIsCameraOrSurveillance(0);
+ }
+ });
+
+ mBotRightCam_Switch = findViewById(R.id.camera_switch3);
+ mBotmRightCam_PictureButton = findViewById(R.id.Picture3);
+ mBotmRightCam_RecordButton = findViewById(R.id.Record3);
+ mBotRightCam_Split = findViewById(R.id.camera_split_view3);
+
+ Buttons[0] = mBotmRightCam_PictureButton;
+ Buttons[1] = mBotmRightCam_RecordButton;
+ Buttons[2] = SettingView3;
+ Buttons[3] = FullScrn3;
+ Buttons[4] = mBotRightCam_Switch;
+ Buttons[5] = mBotRightCam_Split;
+
+ 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);
+
+ ic_camera.setBotRightCam(new CameraBase(this, mBotmRightCam_textureView, Buttons,
+ mRecordingTimeView2, Data, roundedThumbnailView));
+ }
+
+ private void manageTopLeftCam() {
+ frameView0.setVisibility(FrameLayout.VISIBLE);
+ FrameVisibility[0] = frameView0.getVisibility();
+ if (ic_camera.getTopLeftCam() == null) {
+
+ Open_TopLeftCam();
+ } else if (mTopLeftCam_textureView == null) {
+ mTopLeftCam_textureView = findViewById(R.id.textureview0);
+ }
+
+ if (mTopLeftCam_textureView.isAvailable()) {
+ if (ic_camera.getTopLeftCam() != null) {
+ ic_camera.getTopLeftCam().textureListener.onSurfaceTextureAvailable(
+ mTopLeftCam_textureView.getSurfaceTexture(),
+ mTopLeftCam_textureView.getWidth(),
+ mTopLeftCam_textureView.getHeight());
+ }
+ } else {
+ if (ic_camera.getTopLeftCam() != null && mTopLeftCam_textureView != null) {
+ mTopLeftCam_textureView.setSurfaceTextureListener(
+ ic_camera.getTopLeftCam().textureListener);
+ }
+ }
+ findViewById(R.id.camera_switch0).setVisibility(View.VISIBLE);
+ }
+ public void hideCameraSwitchButton() {
+ findViewById(R.id.camera_switch0).setVisibility(View.GONE);
+ findViewById(R.id.camera_switch1).setVisibility(View.GONE);
+ findViewById(R.id.camera_switch2).setVisibility(View.GONE);
+ findViewById(R.id.camera_switch3).setVisibility(View.GONE);
+
+ findViewById(R.id.imageView0).setVisibility(View.VISIBLE);
+ findViewById(R.id.imageView1).setVisibility(View.VISIBLE);
+ findViewById(R.id.imageView2).setVisibility(View.VISIBLE);
+ findViewById(R.id.imageView3).setVisibility(View.VISIBLE);
+ }
+
+ public void visibleCameraSwitchButton() {
+ exitScrn0.setVisibility(View.GONE);
+ exitScrn1.setVisibility(View.GONE);
+ exitScrn2.setVisibility(View.GONE);
+ exitScrn3.setVisibility(View.GONE);
+
+ FullScrn0.setVisibility(View.GONE);
+ FullScrn1.setVisibility(View.GONE);
+ FullScrn2.setVisibility(View.GONE);
+ FullScrn3.setVisibility(View.GONE);
+
+ findViewById(R.id.camera_switch0).setVisibility(View.VISIBLE);
+ findViewById(R.id.camera_switch1).setVisibility(View.VISIBLE);
+ findViewById(R.id.camera_switch2).setVisibility(View.VISIBLE);
+ findViewById(R.id.camera_switch3).setVisibility(View.VISIBLE);
+
+ findViewById(R.id.imageView0).setVisibility(View.GONE);
+ findViewById(R.id.imageView1).setVisibility(View.GONE);
+ findViewById(R.id.imageView2).setVisibility(View.GONE);
+ findViewById(R.id.imageView3).setVisibility(View.GONE);
+ }
+
+ void enableSingleCameraButtons() {
+ findViewById(R.id.camera_switch0).setVisibility(View.VISIBLE);
+ findViewById(R.id.camera_switch1).setVisibility(View.VISIBLE);
+ findViewById(R.id.camera_switch2).setVisibility(View.VISIBLE);
+ findViewById(R.id.camera_switch3).setVisibility(View.VISIBLE);
+
+ exitScrn0.setVisibility(View.GONE);
+ exitScrn1.setVisibility(View.GONE);
+ exitScrn2.setVisibility(View.GONE);
+ exitScrn3.setVisibility(View.GONE);
+
+ FullScrn0.setVisibility(View.GONE);
+ FullScrn1.setVisibility(View.GONE);
+ FullScrn2.setVisibility(View.GONE);
+ FullScrn3.setVisibility(View.GONE);
+
+ }
+ private void manageTopRightCam() {
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+ FrameVisibility[1] = frameView1.getVisibility();
+ if (ic_camera.getTopRightCam() == null) {
+ Open_TopRightCam();
+
+ } else if (mTopRightCam_textureView == null) {
+ mTopRightCam_textureView = findViewById(R.id.textureview1);
+ }
+
+ if (mTopRightCam_textureView.isAvailable()) {
+ ic_camera.getTopRightCam().textureListener.onSurfaceTextureAvailable(
+ mTopRightCam_textureView.getSurfaceTexture(),
+ mTopRightCam_textureView.getWidth(), mTopRightCam_textureView.getHeight());
+ } else {
+ mTopRightCam_textureView.setSurfaceTextureListener(
+ ic_camera.getTopRightCam().textureListener);
+ }
+ }
+
+ private void manageBotmLeftCam() {
+ frameView2.setVisibility(FrameLayout.VISIBLE);
+ FrameVisibility[2] = frameView2.getVisibility();
+ if (ic_camera.getBotLeftCam() == null) {
+ Open_BotmLeftCam();
+
+ } else if (mBotmLeftCam_textureView == null) {
+ mBotmLeftCam_textureView = findViewById(R.id.textureview2);
+ }
+
+ if (mBotmLeftCam_textureView.isAvailable()) {
+ ic_camera.getBotLeftCam().textureListener.onSurfaceTextureAvailable(
+ mBotmLeftCam_textureView.getSurfaceTexture(),
+ mBotmLeftCam_textureView.getWidth(), mBotmLeftCam_textureView.getHeight());
+ } else {
+ mBotmLeftCam_textureView.setSurfaceTextureListener(
+ ic_camera.getBotLeftCam().textureListener);
+ }
+ }
+
+ private void manageBotmRightCam() {
+ frameView3.setVisibility(FrameLayout.VISIBLE);
+ FrameVisibility[3] = frameView3.getVisibility();
+ if (ic_camera.getBotRightCam() == null) {
+ Open_BotmRightCam();
+
+ } else if (mBotmRightCam_textureView == null) {
+ mBotmRightCam_textureView = findViewById(R.id.textureview3);
+ }
+
+ if (mBotmRightCam_textureView.isAvailable()) {
+ ic_camera.getBotRightCam().textureListener.onSurfaceTextureAvailable(
+ mBotmRightCam_textureView.getSurfaceTexture(),
+ mBotmRightCam_textureView.getWidth(), mBotmRightCam_textureView.getHeight());
+ } else {
+ mBotmRightCam_textureView.setSurfaceTextureListener(
+ ic_camera.getBotRightCam().textureListener);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.e(TAG, " onResume");
+ //startCamera();
+ hideCameraSwitchButton();
+ }
+
+ public void closeCamera() {
+ if (null != ic_camera.getTopLeftCam()) {
+ ic_camera.getTopLeftCam().closeCamera();
+ ic_camera.setTopLeftCam(null);
+ }
+
+ if (null != ic_camera.getTopRightCam()) {
+ ic_camera.getTopRightCam().closeCamera();
+ ic_camera.setTopRightCam(null);
+ }
+
+ if (null != ic_camera.getBotRightCam()) {
+ ic_camera.getBotRightCam().closeCamera();
+ ic_camera.setBotRightCam(null);
+ }
+
+ if (null != ic_camera.getBotLeftCam()) {
+ ic_camera.getBotLeftCam().closeCamera();
+ ic_camera.setBotLeftCam(null);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ System.out.println("onPause");
+ super.onPause();
+
+ unregisterReceiver(mUsbReceiver);
+ // 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);
+
+ ic_camera.getTopLeftCam().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);
+
+ ic_camera.getTopRightCam().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);
+
+ ic_camera.getBotLeftCam().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);
+
+ ic_camera.getBotRightCam().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:
+ case R.id.Picture0:
+ ic_camera.setOpenCameraId(0);
+ break;
+ case R.id.Record0:
+ this.setTitle("TopLeftCam");
+ if (exitRecordScrnFlag == true) {
+
+ this.setTitle("MultiCamera");
+ exitRecordScrnFlag = false;
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+ FullScrn0.setVisibility(View.VISIBLE);
+
+ exitScrn0.setVisibility(View.GONE);
+
+ FrameVisibility[1] = frameView1.getVisibility();
+ } else {
+ exitRecordScrnFlag = true;
+ exitScrn0.setVisibility(View.GONE);
+ FullScrn0.setVisibility(View.GONE);
+
+ frameView1.setVisibility(View.GONE);
+
+ LinLayout2.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.exitFullScreen0:
+ this.setTitle("MultiCamera");
+ exitScrnFlag = false;
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+ FullScrn0.setVisibility(View.VISIBLE);
+
+ exitScrn0.setVisibility(View.GONE);
+
+ FrameVisibility[1] = frameView1.getVisibility();
+
+ break;
+ case R.id.imageView1:
+ case R.id.Picture1:
+ ic_camera.setOpenCameraId(1);
+ break;
+ case R.id.Record1:
+ this.setTitle("TopRightCam");
+ if (exitRecordScrnFlag == true) {
+ this.setTitle("MultiCamera");
+ exitRecordScrnFlag = false;
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+
+ 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();
+ } else {
+ exitRecordScrnFlag = true;
+ FullScrn1.setVisibility(View.GONE);
+ exitScrn1.setVisibility(View.GONE);
+
+ frameView0.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.exitFullScreen1:
+ this.setTitle("MultiCamera");
+
+ exitScrnFlag = false;
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+
+ 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();
+ break;
+
+ case R.id.imageView2:
+ case R.id.Picture2:
+ ic_camera.setOpenCameraId(2);
+ break;
+ case R.id.Record2:
+ this.setTitle("BotmLeftCam");
+ if (exitRecordScrnFlag == true) {
+ this.setTitle("MultiCamera");
+
+ exitRecordScrnFlag = false;
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+
+ 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();
+
+ } else {
+ exitRecordScrnFlag = true;
+ FullScrn2.setVisibility(View.GONE);
+ exitScrn2.setVisibility(View.GONE);
+
+ frameView3.setVisibility(View.GONE);
+ LinLayout1.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.exitFullScreen2:
+ this.setTitle("MultiCamera");
+
+ exitScrnFlag = false;
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+
+ 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();
+ break;
+ case R.id.imageView3:
+ case R.id.Picture3:
+ ic_camera.setOpenCameraId(3);
+ break;
+ case R.id.Record3:
+ if (exitRecordScrnFlag == true) {
+ this.setTitle("MultiCamera");
+ exitRecordScrnFlag = false;
+
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+
+ 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();
+ } else {
+ this.setTitle("BotmRightCam");
+ exitRecordScrnFlag = true;
+ FullScrn3.setVisibility(View.GONE);
+ exitScrn3.setVisibility(View.GONE);
+
+ frameView2.setVisibility(View.GONE);
+ LinLayout1.setVisibility(View.GONE);
+ }
+ break;
+ case R.id.exitFullScreen3:
+ this.setTitle("MultiCamera");
+ exitScrnFlag = false;
+
+ LinLayout1.setVisibility(View.GONE);
+ LinLayout2.setVisibility(View.GONE);
+ if (numOfCameras == 1) {
+ frameView1.setVisibility(FrameLayout.GONE);
+ LinLayout1.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 2) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ frameView1.setVisibility(FrameLayout.VISIBLE);
+
+ } else if (numOfCameras == 3) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else if (numOfCameras == 4) {
+ LinLayout1.setVisibility(View.VISIBLE);
+ LinLayout2.setVisibility(View.VISIBLE);
+
+ } else {
+ Log.d(TAG, "onResume No CAMERA CONNECTED");
+ }
+
+ 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();
+ break;
+ default:
+ break;
+ }
+
+ closeCamera();
+
+ Intent intent = new Intent(MultiViewActivity.this, SingleCameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ ic_camera.setIsCameraOrSurveillance(0);
+
+ }
+
+ protected static long getStorageSpaceBytes() {
+ synchronized (mStorageSpaceLock) {
+ return mStorageSpaceBytes;
+ }
+ }
+
+ protected interface OnStorageUpdateDoneListener {
+ void onStorageUpdateDone(long bytes) throws IOException, CameraAccessException;
+ }
+
+ protected static void updateStorageSpace(final OnStorageUpdateDoneListener callback) {
+ /*
+ * We execute disk operations on a background thread in order to
+ * free up the UI thread. Synchronizing on the lock below ensures
+ * that when getStorageSpaceBytes is called, the main thread waits
+ * until this method has completed.
+ *
+ * However, .execute() does not ensure this execution block will be
+ * run right away (.execute() schedules this AsyncTask for sometime
+ * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
+ * tries to execute the task in parellel with other AsyncTasks, but
+ * there's still no guarantee).
+ * e.g. don't call this then immediately call getStorageSpaceBytes().
+ * Instead, pass in an OnStorageUpdateDoneListener.
+ */
+ (new AsyncTask() {
+ @Override
+ protected Long doInBackground(Void... arg) {
+ synchronized (mStorageSpaceLock) {
+ mStorageSpaceBytes = Utils.getAvailableSpace();
+ return mStorageSpaceBytes;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Long bytes) {
+ // This callback returns after I/O to check disk, so we could be
+ // pausing and shutting down. If so, don't bother invoking.
+ if (callback != null) {
+ try {
+ callback.onStorageUpdateDone(bytes);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ } else {
+ Log.v(TAG, "ignoring storage callback after activity pause");
+ }
+ }
+ }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java
new file mode 100644
index 0000000..708fe81
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/PermissionsActivity.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intel.multicamera;
+
+import android.Manifest;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.*;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Window;
+import android.view.WindowManager;
+import androidx.core.app.ActivityCompat;
+import androidx.preference.PreferenceManager;
+
+/**
+ * Activity that shows permissions request dialogs and handles lack of critical permissions.
+ */
+public class PermissionsActivity extends QuickActivity {
+ private String TAG = "PermissionsActivity";
+
+ private static int PERMISSION_REQUEST_CODE = 1;
+
+ private int mIndexPermissionRequestCamera;
+ private int mIndexPermissionRequestMicrophone;
+ private int mIndexPermissionRequestStorage;
+ private boolean mShouldRequestCameraPermission;
+ private boolean mShouldRequestMicrophonePermission;
+ private boolean mShouldRequestStoragePermission;
+ private int mNumPermissionsToRequest;
+ private boolean mFlagHasCameraPermission;
+ private boolean mFlagHasMicrophonePermission;
+ private boolean mFlagHasStoragePermission;
+
+ @Override
+ protected void onCreateTasks(Bundle savedInstanceState) {
+ setContentView(R.layout.permissions);
+
+ Window win = getWindow();
+ win.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+
+ @Override
+ protected void onResumeTasks() {
+ mNumPermissionsToRequest = 0;
+ checkPermissions();
+ }
+
+ @Override
+ protected void onDestroyTasks() {
+ Log.v(TAG, "onDestroy: unregistering receivers");
+ }
+
+ /**
+ * Package private conversion method to turn String storage format into
+ * booleans.
+ *
+ * @param value String to be converted to boolean
+ * @return boolean value of stored String
+ */
+ public boolean convertToBoolean(String value) {
+ boolean ret = false;
+
+ if (value.compareTo("false") == 0) {
+ ret = false;
+
+ } else if (value.compareTo("true") == 0) {
+ ret = true;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Retrieve a setting's value as a String, manually specifiying
+ * a default value.
+ */
+ public String getString(String key, String defaultValue) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ try {
+ return preferences.getString(key, defaultValue);
+ } catch (ClassCastException e) {
+ Log.w(TAG, "existing preference with invalid type,removing and returning default", e);
+ preferences.edit().remove(key).apply();
+ return defaultValue;
+ }
+ }
+
+ private void checkPermissions() {
+ if (ActivityCompat.checkSelfPermission(getApplicationContext(),
+ Manifest.permission.CAMERA) !=
+ PackageManager.PERMISSION_GRANTED) {
+ mNumPermissionsToRequest++;
+ mShouldRequestCameraPermission = true;
+ } else {
+ mFlagHasCameraPermission = true;
+ }
+
+ if (ActivityCompat.checkSelfPermission(getApplicationContext(),
+ Manifest.permission.RECORD_AUDIO) !=
+ PackageManager.PERMISSION_GRANTED) {
+ mNumPermissionsToRequest++;
+ mShouldRequestMicrophonePermission = true;
+ } else {
+ mFlagHasMicrophonePermission = true;
+ }
+
+ if (ActivityCompat.checkSelfPermission(getApplicationContext(),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
+ PackageManager.PERMISSION_GRANTED) {
+ mNumPermissionsToRequest++;
+ mShouldRequestStoragePermission = true;
+ } else {
+ mFlagHasStoragePermission = true;
+ }
+
+ if (mNumPermissionsToRequest != 0) {
+ if (!convertToBoolean(getString("pref_has_seen_permissions_dialogs", "false"))) {
+ buildPermissionsRequest();
+
+ } else {
+ // Permissions dialog has already been shown, or we're on
+ // lockscreen, and we're still missing permissions.
+ handlePermissionsFailure();
+ }
+
+ } else {
+ handlePermissionsSuccess();
+ }
+ }
+
+ private void buildPermissionsRequest() {
+ String[] permissionsToRequest = new String[mNumPermissionsToRequest];
+ int permissionsRequestIndex = 0;
+
+ if (mShouldRequestCameraPermission) {
+ permissionsToRequest[permissionsRequestIndex] = Manifest.permission.CAMERA;
+ mIndexPermissionRequestCamera = permissionsRequestIndex;
+ permissionsRequestIndex++;
+ }
+ if (mShouldRequestMicrophonePermission) {
+ permissionsToRequest[permissionsRequestIndex] = Manifest.permission.RECORD_AUDIO;
+ mIndexPermissionRequestMicrophone = permissionsRequestIndex;
+ permissionsRequestIndex++;
+ }
+ if (mShouldRequestStoragePermission) {
+ permissionsToRequest[permissionsRequestIndex] =
+ Manifest.permission.WRITE_EXTERNAL_STORAGE;
+ mIndexPermissionRequestStorage = permissionsRequestIndex;
+ permissionsRequestIndex++;
+ }
+
+ Log.v(TAG, "requestPermissions count: " + permissionsToRequest.length);
+ requestPermissions(permissionsToRequest, PERMISSION_REQUEST_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[],
+ int[] grantResults) {
+ Log.v(TAG, "onPermissionsResult counts: " + permissions.length + ":" + grantResults.length);
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
+ settings.edit().putString("pref_has_seen_permissions_dialogs", "true").apply();
+
+ if (mShouldRequestCameraPermission) {
+ if (grantResults.length > 0 &&
+ grantResults[mIndexPermissionRequestCamera] == PackageManager.PERMISSION_GRANTED) {
+ mFlagHasCameraPermission = true;
+ } else {
+ handlePermissionsFailure();
+ }
+ }
+ if (mShouldRequestMicrophonePermission) {
+ if (grantResults.length > 0 && grantResults[mIndexPermissionRequestMicrophone] ==
+ PackageManager.PERMISSION_GRANTED) {
+ mFlagHasMicrophonePermission = true;
+ } else {
+ handlePermissionsFailure();
+ }
+ }
+ if (mShouldRequestStoragePermission) {
+ if (grantResults.length > 0 &&
+ grantResults[mIndexPermissionRequestStorage] == PackageManager.PERMISSION_GRANTED) {
+ mFlagHasStoragePermission = true;
+ } else {
+ handlePermissionsFailure();
+ }
+ }
+
+ if (mFlagHasCameraPermission && mFlagHasMicrophonePermission && mFlagHasStoragePermission) {
+ handlePermissionsSuccess();
+ } else {
+ // Permissions dialog has already been shown
+ // and we're still missing permissions.
+ handlePermissionsFailure();
+ }
+ }
+
+ private void handlePermissionsSuccess() {
+ Intent intent = new Intent(this, FullScreenActivity.class);
+ startActivity(intent);
+ finish();
+ }
+
+ private void handlePermissionsFailure() {
+ new AlertDialog.Builder(this)
+ .setTitle(getResources().getString(R.string.camera_error_title))
+ .setMessage(getResources().getString(R.string.error_permissions))
+ .setCancelable(false)
+ .setOnKeyListener(new Dialog.OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ finish();
+ }
+ return true;
+ }
+ })
+ .setNegativeButton(getResources().getString(R.string.dialog_dismiss),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .show();
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/PhotoPreview.java b/camera/MultiCameraApplication/java/com/intel/multicamera/PhotoPreview.java
new file mode 100644
index 0000000..70854e8
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/PhotoPreview.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 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.app.Activity;
+import android.app.Dialog;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.net.Uri;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.Optional;
+
+public class PhotoPreview {
+ private static final String TAG = "PhotoPreview";
+ private Uri mCurrentUri;
+
+ private MultiCamera ic_camera;
+ private RoundedThumbnailView mRoundedThumbnailView;
+ private Activity mActivity;
+ FrameLayout roundedThumbnailViewControlLayout;
+ private String mCameraId;
+ private int mSensorOrientation;
+ private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+
+ 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 PhotoPreview(Activity activity, RoundedThumbnailView roundedThumbnailView, String cameraId) {
+ mActivity = activity;
+ ic_camera = MultiCamera.getInstance();
+ mRoundedThumbnailView = roundedThumbnailView;
+ roundedThumbnailViewControlLayout = mActivity.findViewById(R.id.control1);
+ mCameraId = cameraId;
+ RoundedThumbnail_setOnClickListners();
+ }
+ private void RoundedThumbnail_setOnClickListners() {
+ mRoundedThumbnailView.setCallback(new RoundedThumbnailView.Callback() {
+ @Override
+ public void onHitStateFinished() {
+ System.out.println("RoundedThumbnail_setOnClickListners");
+ ImageView preView;
+ final ImageButton btnDelete, playButton, btnBack, details;
+ FrameLayout previewLayout;
+
+ String mimeType = Utils.getMimeTypeFromURI(mActivity, ic_camera.getCurrentUri());
+
+ 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);
+ details = mActivity.findViewById(R.id.control_info);
+ details.setVisibility(View.VISIBLE);
+
+ 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 = ic_camera.getCurrentUri();
+ File file = new File(Utils.getRealPathFromURI(mActivity, uri));
+ if (file.exists()) {
+ Log.v(TAG, " Thumbnail Preview File Deleted ");
+ file.delete();
+ // request scan
+ Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ scanIntent.setData(Uri.fromFile(file));
+ mActivity.sendBroadcast(scanIntent);
+ preView.setImageResource(android.R.color.background_dark);
+ details.setVisibility(View.INVISIBLE);
+ }
+ }
+ });
+
+ details.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ showDetailsDialog(ic_camera.getCurrentFileInfo());
+ }
+ });
+
+ if (mimeType.compareTo("video/mp4") == 0) {
+ VideoPreview(playButton, preView);
+
+ } else {
+ photoPreview(playButton, preView);
+ }
+ }
+ });
+ }
+
+
+ private void showDetailsDialog(ContentValues info) {
+ Optional details = Utils.getMediaDetails(mActivity, info);
+ if (!details.isPresent()) {
+ return;
+ }
+ Dialog detailDialog = DetailsDialog.create(mActivity, details.get());
+ detailDialog.show();
+ }
+
+ private void VideoPreview(ImageButton playButton, ImageView preView) {
+ final Optional bitmap =
+ Utils.getVideoThumbnail(mActivity.getContentResolver(), ic_camera.getCurrentUri());
+ if (bitmap.isPresent()) {
+ playButton.setVisibility(View.VISIBLE);
+
+ playButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Utils.playVideo(mActivity, ic_camera.getCurrentUri(), TAG);
+ }
+ });
+
+ preView.setVisibility(View.VISIBLE);
+ preView.setImageBitmap(bitmap.get());
+
+ } else {
+ Log.e(TAG, "No bitmap image found: ");
+ }
+ }
+
+ private void photoPreview(ImageButton playButton, ImageView preView) {
+ Uri PhotoUri = ic_camera.getCurrentUri();
+
+ preView.setVisibility(View.VISIBLE);
+ playButton.setVisibility(View.GONE);
+ preView.setImageURI(PhotoUri);
+ }
+
+ public void showImageThumbnail(File ImageFile) {
+ final int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+ final Optional bitmap =
+ Utils.generateThumbnail(ImageFile, roundedThumbnailViewControlLayout.getWidth(),
+ roundedThumbnailViewControlLayout.getMeasuredHeight());
+
+ if (bitmap.isPresent()) {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRoundedThumbnailView.startRevealThumbnailAnimation("photo taken");
+ mRoundedThumbnailView.setThumbnail(bitmap.get(), getOrientation(rotation));
+ }
+ });
+
+ } else {
+ Log.e(TAG, "No bitmap image found: ");
+ }
+ }
+
+ 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.
+ try {
+ CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
+ mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ } catch (Exception e) {
+
+ }
+ return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
+ }
+
+
+ public void showVideoThumbnail() {
+ mRoundedThumbnailView.startRevealThumbnailAnimation("Video taken");
+
+ final Optional bitmap =
+ Utils.getVideoThumbnail(mActivity.getContentResolver(), ic_camera.getCurrentUri());
+
+ if (bitmap.isPresent()) {
+ mRoundedThumbnailView.setThumbnail(bitmap.get(), 0);
+ } else {
+ Log.e(TAG, "No bitmap image found: ");
+ }
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java
new file mode 100644
index 0000000..a2b99da
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/QuickActivity.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.intel.multicamera;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * Workaround for lockscreen double-onResume() bug:
+ *
+ * We track 3 startup situations:
+ *
+ * - Normal startup -- e.g. from GEL.
+ * - Secure lock screen startup -- e.g. with a keycode.
+ * - Non-secure lock screen startup -- e.g. with just a swipe.
+ *
+ * The KeyguardManager service can be queried to determine which state we are in.
+ * If started from the lock screen, the activity may be quickly started,
+ * resumed, paused, stopped, and then started and resumed again. This is
+ * problematic for launch time from the lock screen because we typically open the
+ * camera in onResume() and close it in onPause(). These camera operations take
+ * a long time to complete. To workaround it, this class filters out
+ * high-frequency onResume()->onPause() sequences if the KeyguardManager
+ * indicates that we have started from the lock screen.
+ *
+ *
+ * Subclasses should override the appropriate on[Create|Start...]Tasks() method
+ * in place of the original.
+ *
+ *
+ * Sequences of onResume() followed quickly by onPause(), when the activity is
+ * started from a lockscreen will result in a quick no-op.
+ *
+ */
+public abstract class QuickActivity extends Activity {
+ private String TAG = "QuickActivity";
+
+ /**
+ * onResume tasks delay from secure lockscreen.
+ */
+ private static final long ON_RESUME_DELAY_SECURE_MILLIS = 30;
+ /**
+ * onResume tasks delay from non-secure lockscreen.
+ */
+ private static final long ON_RESUME_DELAY_NON_SECURE_MILLIS = 15;
+
+ /**
+ * A reference to the main handler on which to run lifecycle methods.
+ */
+ private Handler mMainHandler;
+
+ /**
+ * True if onResume tasks have been skipped, and made false again once they
+ * are executed within the onResume() method or from a delayed Runnable.
+ */
+ private boolean mSkippedFirstOnResume = false;
+
+ /**
+ * When application execution started in SystemClock.elapsedRealtimeNanos().
+ */
+ protected long mExecutionStartNanoTime = 0;
+ /**
+ * Was this session started with onCreate().
+ */
+ protected boolean mStartupOnCreate = false;
+
+ /**
+ * A runnable for deferring tasks to be performed in onResume() if starting
+ * from the lockscreen.
+ */
+ private final Runnable mOnResumeTasks = new Runnable() {
+ @Override
+ public void run() {
+ if (mSkippedFirstOnResume) {
+ Log.v(TAG, "delayed Runnable --> onResumeTasks()");
+ // Doing the tasks, can set to false.
+ mSkippedFirstOnResume = false;
+ onResumeTasks();
+ }
+ }
+ };
+
+ @Override
+ protected final void onNewIntent(Intent intent) {
+ logLifecycle("onNewIntent", true);
+ Log.v(TAG, "Intent Action = " + intent.getAction());
+ setIntent(intent);
+ super.onNewIntent(intent);
+ onNewIntentTasks(intent);
+ logLifecycle("onNewIntent", false);
+ }
+
+ @Override
+ protected final void onCreate(Bundle bundle) {
+ mExecutionStartNanoTime = SystemClock.elapsedRealtimeNanos();
+ logLifecycle("onCreate", true);
+ mStartupOnCreate = true;
+ super.onCreate(bundle);
+ mMainHandler = new Handler(getMainLooper());
+ onCreateTasks(bundle);
+ logLifecycle("onCreate", false);
+ }
+
+ @Override
+ protected final void onStart() {
+ logLifecycle("onStart", true);
+ onStartTasks();
+ super.onStart();
+ logLifecycle("onStart", false);
+ }
+
+ @Override
+ protected final void onResume() {
+ logLifecycle("onResume", true);
+
+ // For lockscreen launch, there are two possible flows:
+ // 1. onPause() does not occur before mOnResumeTasks is executed:
+ // Runnable mOnResumeTasks sets mSkippedFirstOnResume to false
+ // 2. onPause() occurs within ON_RESUME_DELAY_*_MILLIS:
+ // a. Runnable mOnResumeTasks is removed
+ // b. onPauseTasks() is skipped, mSkippedFirstOnResume remains true
+ // c. next onResume() will immediately execute onResumeTasks()
+ // and set mSkippedFirstOnResume to false
+
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ if (mSkippedFirstOnResume == false) {
+ long delay = mSkippedFirstOnResume ? ON_RESUME_DELAY_SECURE_MILLIS
+ : ON_RESUME_DELAY_NON_SECURE_MILLIS;
+ // Skipping onResumeTasks; set to true.
+ mSkippedFirstOnResume = true;
+ Log.v(TAG, "onResume() --> postDelayed(mOnResumeTasks," + delay + ")");
+ mMainHandler.postDelayed(mOnResumeTasks, delay);
+ } else {
+ Log.v(TAG, "onResume --> onResumeTasks()");
+ // Doing the tasks, can set to false.
+ mSkippedFirstOnResume = false;
+ onResumeTasks();
+ }
+ super.onResume();
+ logLifecycle("onResume", false);
+ }
+
+ @Override
+ protected final void onPause() {
+ logLifecycle("onPause", true);
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ // Only run onPauseTasks if we have not skipped onResumeTasks in a
+ // first call to onResume. If we did skip onResumeTasks (note: we
+ // just killed any delayed Runnable), we also skip onPauseTasks to
+ // adhere to lifecycle state machine.
+ if (mSkippedFirstOnResume == false) {
+ Log.v(TAG, "onPause --> onPauseTasks()");
+ onPauseTasks();
+ }
+ super.onPause();
+ mStartupOnCreate = false;
+ logLifecycle("onPause", false);
+ }
+
+ @Override
+ protected final void onStop() {
+ if (isChangingConfigurations()) {
+ Log.v(TAG, "changing configurations");
+ }
+ logLifecycle("onStop", true);
+ onStopTasks();
+ super.onStop();
+ logLifecycle("onStop", false);
+ }
+
+ @Override
+ protected final void onRestart() {
+ logLifecycle("onRestart", true);
+ super.onRestart();
+ // TODO Support onRestartTasks() and handle the workaround for that too.
+ logLifecycle("onRestart", false);
+ }
+
+ @Override
+ protected final void onDestroy() {
+ logLifecycle("onDestroy", true);
+ onDestroyTasks();
+ super.onDestroy();
+ logLifecycle("onDestroy", false);
+ }
+
+ private void logLifecycle(String methodName, boolean start) {
+ String prefix = start ? "START" : "END";
+ Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString());
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onNewIntent}.
+ */
+ protected void onNewIntentTasks(Intent newIntent) {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onCreate}.
+ */
+ protected void onCreateTasks(Bundle savedInstanceState) {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onStart}.
+ */
+ protected void onStartTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onResume}.
+ */
+ protected void onResumeTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onPause}.
+ */
+ protected void onPauseTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onStop}.
+ */
+ protected void onStopTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onDestroy}.
+ */
+ protected void onDestroyTasks() {
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/RoundedThumbnailView.java b/camera/MultiCameraApplication/java/com/intel/multicamera/RoundedThumbnailView.java
new file mode 100644
index 0000000..1c801ba
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/RoundedThumbnailView.java
@@ -0,0 +1,764 @@
+/*
+ * 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();
+ }
+
+ 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
new file mode 100644
index 0000000..d2fef0f
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.util.Log;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceFragment;
+
+public class SettingsActivity extends AppCompatActivity {
+ private String TAG = "settings";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.settings_activity);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.settings, new SettingsFragment())
+ .commit();
+ }
+
+ public static class SettingsFragment
+ extends PreferenceFragment implements OnSharedPreferenceChangeListener {
+ public String TAG = "SettingsFragment";
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.settings, rootKey);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // final Activity activity = this.getActivity();
+
+ Log.d(TAG, "SettingsFragment onResume");
+
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(
+ this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
+ this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String Key) {
+ }
+ }
+}
\ 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..6b7d026
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SettingsPrefUtil.java
@@ -0,0 +1,584 @@
+/*
+ * 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.Rational;
+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.math.BigInteger;
+import java.text.DecimalFormat;
+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_UHD4K = "UHD 4K";
+ public static final String SIZE_FHD = "FHD 1080p";
+ public static final String SIZE_HD = "HD 720p";
+ public static final String SIZE_VGA = "SD 480p";
+
+ public String[] mCamcorderProfileNames;
+ private static final String SIZE_SETTING_STRING_DIMENSION_DELIMITER = "x";
+
+ private String CameraId;
+ private int root_preferences;
+ private String pref_resolution;
+ private String video_list, capture_list;
+ public List PictureSizes;
+ private ArrayList VideoEntries;
+ 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);
+
+ public static int getFromSetting(String videoQuality) {
+ // Sanitize the value to be either small, medium or large. Default
+ // to the latter.
+
+ if (SIZE_UHD4K.equals(videoQuality)) {
+ return CamcorderProfile.QUALITY_2160P;
+ } else if (SIZE_FHD.equals(videoQuality)) {
+ return CamcorderProfile.QUALITY_1080P;
+ } else if (SIZE_HD.equals(videoQuality)) {
+ return CamcorderProfile.QUALITY_720P;
+ } else if (SIZE_VGA.equals(videoQuality)) {
+ return CamcorderProfile.QUALITY_480P;
+ }
+
+ return CamcorderProfile.QUALITY_480P;
+ }
+
+ 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) {
+ Log.d(TAG, "fail to get supported size");
+ return;
+ }
+
+ ImageDimentions = new ArrayList<>(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)));
+
+ PictureSizes = new ArrayList(Arrays.asList());
+ VideoEntries = new ArrayList<>();
+
+ 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);
+ if (CamcorderProfile.hasProfile(0, sVideoQualities[3])) {
+ VideoEntries.add(mCamcorderProfileNames[sVideoQualities[3]]);
+ }
+
+ } else if (size.equals(SIZE_720P)) {
+ PictureSizes.add(SIZE_720P);
+ if (CamcorderProfile.hasProfile(0, sVideoQualities[2])) {
+ VideoEntries.add(mCamcorderProfileNames[sVideoQualities[2]]);
+ }
+
+ } else if (size.equals(SIZE_1080P)) {
+ PictureSizes.add(SIZE_1080P);
+ if (CamcorderProfile.hasProfile(0, sVideoQualities[1])) {
+ VideoEntries.add(mCamcorderProfileNames[sVideoQualities[1]]);
+ }
+
+ } else if (size.equals(SIZE_4K)) {
+ PictureSizes.add(SIZE_4K);
+ if (CamcorderProfile.hasProfile(0, sVideoQualities[0])) {
+ VideoEntries.add(mCamcorderProfileNames[sVideoQualities[0]]);
+ }
+ }
+ }
+
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Video qualities sorted by size.
+ */
+ public static int[] sVideoQualities =
+ new int[] {// CamcorderProfile.QUALITY_HIGH,
+ CamcorderProfile.QUALITY_2160P, CamcorderProfile.QUALITY_1080P,
+ CamcorderProfile.QUALITY_720P, CamcorderProfile.QUALITY_480P,
+ CamcorderProfile.QUALITY_CIF, CamcorderProfile.QUALITY_QVGA,
+ CamcorderProfile.QUALITY_QCIF, CamcorderProfile.QUALITY_LOW};
+
+ @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;
+ }
+
+ 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();
+
+ Log.d(TAG, " @@ SettingsFragment onResume @@" + CameraId);
+
+ 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();
+
+ 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(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 preference The preference for which to set the summary.
+ */
+ private void setSummaryForSelection(ListPreference preference) {
+ if (preference == null) {
+ return;
+ }
+
+ int selectedQuality = 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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(VideoEntries, 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 entries The possible S,M,L entries the user can
+ * choose from.
+ * @param preference The preference to set the entries for.
+ */
+ private void setEntriesForSelection(ArrayList entries, ListPreference preference) {
+ if (entries == null) {
+ return;
+ }
+
+ preference.setEntries(entries.toArray(new String[0]));
+ }
+
+ /**
+ * Different aspect ratio constants.
+ */
+ public static final Rational ASPECT_RATIO_16x9 = new Rational(16, 9);
+ public static final Rational ASPECT_RATIO_4x3 = new Rational(4, 3);
+ private static final double ASPECT_RATIO_TOLERANCE = 0.05;
+
+ /**
+ * These are the preferred aspect ratios for the settings. We will take HAL
+ * supported aspect ratios that are within ASPECT_RATIO_TOLERANCE of these values.
+ * We will also take the maximum supported resolution for full sensor image.
+ */
+ private static Float[] sDesiredAspectRatios = {16.0f / 9.0f, 4.0f / 3.0f};
+
+ private static Size[] sDesiredAspectRatioSizes = {new Size(16, 9), new Size(4, 3)};
+
+ /**
+ * Take an aspect ratio and squish it into a nearby desired aspect ratio, if
+ * possible.
+ *
+ * @param aspectRatio the aspect ratio to fuzz
+ * @return the closest desiredAspectRatio within ASPECT_RATIO_TOLERANCE, or the
+ * original ratio
+ */
+ private static float fuzzAspectRatio(float aspectRatio) {
+ for (float desiredAspectRatio : sDesiredAspectRatios) {
+ if ((Math.abs(aspectRatio - desiredAspectRatio)) < ASPECT_RATIO_TOLERANCE) {
+ return desiredAspectRatio;
+ }
+ }
+ return aspectRatio;
+ }
+
+ /**
+ * Reduce an aspect ratio to its lowest common denominator. The ratio of the
+ * input and output sizes is guaranteed to be the same.
+ *
+ * @param aspectRatio the aspect ratio to reduce
+ * @return The reduced aspect ratio which may equal the original.
+ */
+ public static Size reduce(Size aspectRatio) {
+ BigInteger width = BigInteger.valueOf(aspectRatio.getWidth());
+ BigInteger height = BigInteger.valueOf(aspectRatio.getHeight());
+ BigInteger gcd = width.gcd(height);
+ int numerator = Math.max(width.intValue(), height.intValue()) / gcd.intValue();
+ int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
+ return new Size(numerator, denominator);
+ }
+
+ /**
+ * Given a size, return the closest aspect ratio that falls close to the
+ * given size.
+ *
+ * @param size the size to approximate
+ * @return the closest desired aspect ratio, or the original aspect ratio if
+ * none were close enough
+ */
+ public static Size getApproximateSize(Size size) {
+ Size aspectRatio = reduce(size);
+ float fuzzy = fuzzAspectRatio(size.getWidth() / (float)size.getHeight());
+ int index = Arrays.asList(sDesiredAspectRatios).indexOf(fuzzy);
+ if (index != -1) {
+ aspectRatio = sDesiredAspectRatioSizes[index];
+ }
+ return aspectRatio;
+ }
+
+ private static DecimalFormat sMegaPixelFormat = new DecimalFormat("##0.0");
+
+ /**
+ * Given a size return the numerator of its aspect ratio
+ *
+ * @param size the size to measure
+ * @return the numerator
+ */
+ public static int aspectRatioNumerator(Size size) {
+ Size aspectRatio = reduce(size);
+ return aspectRatio.getWidth();
+ }
+
+ /**
+ * Given a size return the numerator of its aspect ratio
+ *
+ * @param size
+ * @return the denominator
+ */
+ public static int aspectRatioDenominator(Size size) {
+ BigInteger width = BigInteger.valueOf(size.getWidth());
+ BigInteger height = BigInteger.valueOf(size.getHeight());
+ BigInteger gcd = width.gcd(height);
+ int denominator = Math.min(width.intValue(), height.intValue()) / gcd.intValue();
+ return denominator;
+ }
+
+ /**
+ * @param size The photo resolution.
+ * @return A human readable and translated string for labeling the
+ * picture size in megapixels.
+ */
+ private String getSizeSummaryString(Size size) {
+ Size approximateSize = getApproximateSize(size);
+ String megaPixels = sMegaPixelFormat.format((size.getWidth() * size.getHeight()) / 1e6);
+ int numerator = aspectRatioNumerator(approximateSize);
+ int denominator = aspectRatioDenominator(approximateSize);
+ String result =
+ getResources().getString(R.string.setting_summary_aspect_ratio_and_megapixels,
+ numerator, denominator, megaPixels);
+ return result;
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/SingleCameraActivity.java b/camera/MultiCameraApplication/java/com/intel/multicamera/SingleCameraActivity.java
new file mode 100644
index 0000000..f07a2a7
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/SingleCameraActivity.java
@@ -0,0 +1,333 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
+import android.hardware.usb.UsbManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static com.intel.multicamera.MultiViewActivity.updateStorageSpace;
+
+public class SingleCameraActivity extends AppCompatActivity {
+ private static final String TAG = "SingleCameraActivity";
+ private boolean mHasCriticalPermissions;
+
+ private int numOfCameras;
+ private AutoFitTextureView mCamera_BackView, mCamera_FrontView;
+
+ private ImageButton mCameraSwitch, mCameraPicture, mCameraRecord, mCameraSplit, mSettings;
+ public String[] CameraIds;
+ MultiCamera mCameraInst;
+ private TextView mRecordingTimeView;
+ private boolean mIsRecordingVideo;
+ private CameraBase mCamera;
+ private RoundedThumbnailView mRoundedThumbnailView;
+ private BroadcastReceiver mUsbReceiver;
+ private long mLastClickTime = 0;
+
+
+ @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_full_screen);
+
+ mIsRecordingVideo = false;
+ mCameraInst = MultiCamera.getInstance();
+ mCameraSwitch = findViewById(R.id.camera_switch);
+ mCameraSwitch.setVisibility(View.VISIBLE);
+ if (mCameraInst.getOpenCameraId() == 2 || mCameraInst.getOpenCameraId() == 3)
+ mCameraSwitch.setVisibility(View.GONE);
+ mCameraPicture = findViewById(R.id.Picture);
+ mCameraRecord = findViewById(R.id.Record);
+ mCameraSplit = findViewById(R.id.camera_split_view);
+ mRecordingTimeView = findViewById(R.id.recording_time);
+ mSettings = findViewById(R.id.SettingView);
+ mRoundedThumbnailView = findViewById(R.id.rounded_thumbnail_view);
+
+ checkPermissions();
+ OpenCamera();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+
+ // BroadcastReceiver when insert/remove the device USB plug into/from a USB port
+ mUsbReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ System.out.println("BroadcastReceiver Event");
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
+ System.out.println(TAG+"BroadcastReceiver USB Connected");
+ OpenCamera();
+
+ } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
+ System.out.println(TAG+"BroadcastReceiver USB Disconnected");
+ OpenCamera();
+ }
+
+ }
+ };
+
+ registerReceiver(mUsbReceiver , filter);
+
+ mCameraSwitch.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
+ return;
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+ MultiCamera ic_camera = MultiCamera.getInstance();
+ if (ic_camera.getWhichCamera() == 0) {
+ ic_camera.setOpenCameraId(1);
+ OpenCamera();
+ ic_camera.setWhichCamera(1);
+ Log.i(TAG,"Opened front camera");
+ }
+ else {
+ ic_camera.setOpenCameraId(0);
+ OpenCamera();
+ ic_camera.setWhichCamera(0);
+ Log.i(TAG,"Opened back camera");
+ }
+ }
+ });
+
+ mCameraSplit.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MultiCamera ic_camera = MultiCamera.getInstance();
+ ic_camera.setIsCameraOrSurveillance(1);
+ Intent intent = new Intent(SingleCameraActivity.this, MultiViewActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+
+ }
+ });
+
+ mCameraPicture.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
+ return;
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+ mCamera.takePicture();
+
+ }
+ });
+
+ mCameraRecord.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
+ return;
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+
+ try {
+ if (mIsRecordingVideo) {
+ mIsRecordingVideo = false;
+
+ mCamera.getmRecord().stopRecordingVideo();
+ mCamera.getmRecord().showRecordingUI(false);
+ mCamera.getmPhoto().showVideoThumbnail();
+
+ mCameraSwitch.setVisibility(View.VISIBLE);
+ mCameraPicture.setVisibility(View.VISIBLE);
+ mCameraRecord.setImageResource(R.drawable.ic_capture_video);
+ mCameraSplit.setVisibility(View.VISIBLE);
+ mSettings.setVisibility(View.VISIBLE);
+ } else {
+ mIsRecordingVideo =true;
+
+ mCamera.getmRecord().startRecordingVideo();
+
+ mCamera.getmRecord().showRecordingUI(true);
+ mSettings.setVisibility(View.GONE);
+ mCameraSwitch.setVisibility(View.GONE);
+ mCameraPicture.setVisibility(View.GONE);
+ mCameraRecord.setImageResource(R.drawable.ic_stop_normal);
+ mCameraSplit.setVisibility(View.GONE);
+ }
+ } catch (Exception e) {
+ System.out.println(TAG + "exception during record");
+ }
+ }
+ });
+ }
+
+ public void OpenCamera() {
+ closeCamera();
+
+ GetCameraCnt();
+ if (numOfCameras == 0) {
+ Toast.makeText(SingleCameraActivity.this, "No Camera Found", Toast.LENGTH_LONG).show();
+ System.out.println(TAG+" no camera found");
+ return;
+ }
+ if (numOfCameras == 1) {
+ findViewById(R.id.camera_switch).setVisibility(View.GONE);
+ findViewById(R.id.camera_split_view).setVisibility(View.GONE);
+ }
+
+ updateStorageSpace(null);
+
+ Open_Camera();
+ }
+
+ private void closeCamera() {
+
+ if (null != mCamera) {
+ mCamera.closeCamera();
+ mCamera = null;
+ }
+ }
+
+
+ public void GetCameraCnt() {
+ CameraManager manager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
+
+ try {
+ CameraIds = manager.getCameraIdList();
+ numOfCameras = manager.getCameraIdList().length;
+ if (numOfCameras == 0) {
+ Toast.makeText(SingleCameraActivity.this, "No camera found, closing the application",
+ Toast.LENGTH_LONG).show();
+ }
+ Log.d(TAG, "Get total number of cameras present: " + manager.getCameraIdList().length);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void Open_Camera() {
+ mCamera_BackView = findViewById(R.id.textureview);
+ if (mCamera_BackView == null) {
+ Log.e(TAG, "fail to find surface for back camera");
+ return;
+ }
+
+ if (mCamera == null) {
+ Open_Camera_ById();
+ }
+ if (mCamera_BackView.isAvailable()) {
+ mCamera.textureListener.onSurfaceTextureAvailable(
+ mCamera_BackView.getSurfaceTexture(), mCamera_BackView.getWidth(),
+ mCamera_BackView.getHeight());
+ } else {
+ mCamera_BackView.setSurfaceTextureListener(mCamera.textureListener);
+ }
+ }
+
+ public void Open_Camera_ById() {
+ String[] Data = new String[5];
+
+ Data[0] = "BackCamera";
+ Data[1] = CameraIds[mCameraInst.getOpenCameraId()];
+ Data[2] = "capture_list";
+ Data[3] = "video_list";
+ Data[4] = "pref_resolution_1";
+
+ mCamera = new CameraBase(this, mCamera_BackView, null, mRecordingTimeView,
+ Data, mRoundedThumbnailView);
+ }
+
+ /**
+ * 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() {
+ mHasCriticalPermissions =
+ 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;
+
+ if (!mHasCriticalPermissions) {
+ Intent intent = new Intent(this, PermissionsActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+
+ if (!mHasCriticalPermissions) {
+ Log.v(TAG, "onCreate: Missing critical permissions.");
+ finish();
+ return;
+ }
+
+ }
+
+
+ @Override
+ protected void onPause() {
+ unregisterReceiver(mUsbReceiver);
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.e(TAG, " onResume");
+ }
+}
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/Utils.java b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java
new file mode 100644
index 0000000..98d72fe
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/Utils.java
@@ -0,0 +1,655 @@
+/*
+ * 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.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.*;
+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.os.StatFs;
+import android.provider.MediaStore;
+import android.text.format.DateUtils;
+import android.util.Log;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import javax.microedition.khronos.opengles.GL11;
+
+public class Utils {
+ private static final String TAG = "Utils";
+
+ public static final String DCIM =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
+ public static final String DIRECTORY = DCIM + "/MultiCamera";
+
+ public static final int MEDIA_TYPE_IMAGE = 0;
+ public static final int MEDIA_TYPE_VIDEO = 1;
+
+ 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.
+ */
+ public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+ /**
+ * 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.
+ public static final long UNAVAILABLE = -1L;
+ public static final long PREPARING = -2L;
+ public static final long UNKNOWN_SIZE = -3L;
+ public static final long LOW_STORAGE_THRESHOLD_BYTES = 50000000;
+
+ 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;
+ private static ContentValues mCurrentPicInfo, mCurrentVideoInfo;
+
+ @SuppressLint("SimpleDateFormat")
+ public static File createOutputmediaStorageDir() {
+ // To be safe, you should check that the SDCard is mounted
+ // using Environment.getExternalStorageState() before doing this.
+
+ String state = Environment.getExternalStorageState();
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ Log.e(TAG, "getExternalStorageState failed");
+ return null;
+ }
+
+ File mediaStorageDir = new File(DIRECTORY);
+ // This location works best if you want the created images to be shared
+ // between applications and persist after your app has been uninstalled.
+
+ // Create the storage directory if it does not exist
+ if (!mediaStorageDir.exists()) {
+ if (!mediaStorageDir.mkdirs()) {
+ Log.e(TAG, "Failed to create directory for " + DIRECTORY);
+ return null;
+ }
+ }
+
+ return mediaStorageDir;
+ }
+
+ public static Uri broadcastNewPicture(Context context, ContentValues values) {
+ Uri uri = null;
+ ContentResolver resolver = context.getContentResolver();
+ try {
+ uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new ContentValues(values));
+ } catch (Throwable th) {
+ // This can happen when the external volume is already mounted, but
+ // MediaScanner has not notify MediaProvider to add that volume.
+ // The picture is still safe and MediaScanner will find it and
+ // insert it into MediaProvider. The only problem is that the user
+ // cannot click the thumbnail to review the picture.
+ Log.e(TAG, "Failed to write MediaStore" + th);
+ } finally {
+ Log.v(TAG, "Current Picture URI: " + uri);
+ }
+
+ context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
+ return uri;
+ }
+
+ public static Uri broadcastNewVideo(Context context, ContentValues values) {
+ Uri uri = null;
+ ContentResolver resolver = context.getContentResolver();
+ try {
+ Uri videoTable = Uri.parse(VIDEO_BASE_URI);
+ uri = resolver.insert(videoTable, new ContentValues(values));
+ } catch (Exception e) {
+ // We failed to insert into the database. This can happen if
+ // the SD card is unmounted.
+ Log.e(TAG, "failed to add video to media store", e);
+ uri = null;
+ } finally {
+ Log.v(TAG, "Current video URI: " + uri);
+ }
+
+ context.sendBroadcast(new Intent(ACTION_NEW_VIDEO, uri));
+ return uri;
+ }
+
+ public static String getFileNameFromUri(Uri uri) {
+ String result = null;
+ result = uri.getPath();
+ int cut = result.lastIndexOf('/');
+ if (cut != -1) {
+ result = result.substring(cut + 1);
+ }
+ return result;
+ }
+
+ public static String[] generateFileDetails(int type) {
+ File mediaStorageDir = createOutputmediaStorageDir();
+ if (mediaStorageDir == null) {
+ Log.e(TAG, "createOutputmediaStorageDir failed");
+ return null;
+ }
+
+ long dateTaken = System.currentTimeMillis();
+ Date date = new Date(dateTaken);
+ SimpleDateFormat dateFormat;
+ String fileDetails[] = new String[5];
+ if (type == MEDIA_TYPE_IMAGE) {
+ dateFormat = new SimpleDateFormat(IMAGE_FILE_NAME_FORMAT);
+ fileDetails[0] = dateFormat.format(date);
+ fileDetails[1] = fileDetails[0] + ".jpg";
+ fileDetails[2] = "image/jpeg";
+ } else if (type == MEDIA_TYPE_VIDEO) {
+ dateFormat = new SimpleDateFormat(VIDEO_FILE_NAME_FORMAT);
+ fileDetails[0] = dateFormat.format(date);
+ fileDetails[1] = fileDetails[0] + ".mp4";
+ fileDetails[2] = "video/mp4";
+ } else {
+ Log.e(TAG, "Invalid Media Type: " + type);
+ return null;
+ }
+
+ fileDetails[3] = mediaStorageDir.getPath() + '/' + fileDetails[1];
+ fileDetails[4] = Long.toString(dateTaken);
+ Log.v(TAG, "Generated filename: " + fileDetails[3]);
+ return fileDetails;
+ }
+
+ public static ContentValues getContentValues(int type, String[] fileDetails, int width,
+ int height, long duration, long size) {
+ if (fileDetails.length < 5) {
+ Log.e(TAG, "Invalid file details");
+ return null;
+ }
+
+ File file = new File(fileDetails[3]);
+ long dateModifiedSeconds = TimeUnit.MILLISECONDS.toSeconds(file.lastModified());
+
+ ContentValues contentValue = null;
+ if (MEDIA_TYPE_IMAGE == type) {
+ contentValue = new ContentValues(9);
+ contentValue.put(MediaStore.Images.ImageColumns.TITLE, fileDetails[0]);
+ contentValue.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileDetails[1]);
+ contentValue.put(MediaStore.Images.ImageColumns.DATE_TAKEN,
+ Long.valueOf(fileDetails[4]));
+ contentValue.put(MediaStore.Images.ImageColumns.MIME_TYPE, fileDetails[2]);
+ contentValue.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateModifiedSeconds);
+ 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]);
+ contentValue.put(MediaStore.Video.Media.DISPLAY_NAME, fileDetails[1]);
+ contentValue.put(MediaStore.Video.Media.DATE_TAKEN, Long.valueOf(fileDetails[4]));
+ contentValue.put(MediaStore.MediaColumns.DATE_MODIFIED,
+ Long.valueOf(fileDetails[4]) / 1000);
+ contentValue.put(MediaStore.Video.Media.MIME_TYPE, fileDetails[2]);
+ contentValue.put(MediaStore.Video.Media.DATA, fileDetails[3]);
+ contentValue.put(MediaStore.Video.Media.WIDTH, width);
+ 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);
+
+ try {
+ stream.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Fail to close stream");
+ }
+
+ 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;
+ }
+
+ public static Optional getMediaDetails(Context mContext, ContentValues info) {
+ MediaDetails mediaDetails = new MediaDetails();
+ final DateFormat mDateFormatter = DateFormat.getDateTimeInstance();
+
+ if (info.get(MediaStore.Video.Media.MIME_TYPE).equals("video/mp4") == true) {
+ File file = new File(info.getAsString(MediaStore.Video.Media.DATA));
+
+ mediaDetails.addDetail(MediaDetails.INDEX_TITLE,
+ info.get(MediaStore.Video.Media.TITLE));
+ mediaDetails.addDetail(MediaDetails.INDEX_PATH, info.get(MediaStore.Video.Media.DATA));
+ long mSizeInBytes = info.getAsLong(MediaStore.Video.Media.SIZE);
+ if (mSizeInBytes > 0) {
+ mediaDetails.addDetail(MediaDetails.INDEX_SIZE, mSizeInBytes);
+ }
+
+ String Dimensions = MediaDetails.getDimentions(
+ mContext, info.getAsInteger(MediaStore.Video.Media.WIDTH),
+ info.getAsInteger(MediaStore.Video.Media.HEIGHT));
+
+ mediaDetails.addDetail(MediaDetails.INDEX_DIMENSIONS, Dimensions);
+
+ mediaDetails.addDetail(MediaDetails.INDEX_TYPE,
+ info.get(MediaStore.Video.Media.MIME_TYPE));
+
+ String dateModified = DateUtils.formatDateTime(
+ mContext, info.getAsLong(MediaStore.Video.Media.DATE_TAKEN),
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE |
+ DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_ALL);
+
+ mediaDetails.addDetail(MediaDetails.INDEX_DATETIME, dateModified);
+
+ String duration = MediaDetails.formatDuration(
+ mContext, TimeUnit.MILLISECONDS.toSeconds(
+ (Long)info.get(MediaStore.Video.Media.DURATION)));
+ mediaDetails.addDetail(MediaDetails.INDEX_DURATION, duration);
+
+ } else if (info.get(MediaStore.Video.Media.MIME_TYPE).equals("image/jpeg") == true) {
+ File file = new File(info.getAsString(MediaStore.Images.ImageColumns.DATA));
+ mediaDetails.addDetail(MediaDetails.INDEX_TITLE,
+ info.get(MediaStore.Images.ImageColumns.TITLE));
+ mediaDetails.addDetail(MediaDetails.INDEX_PATH,
+ info.get(MediaStore.Images.ImageColumns.DATA));
+ String Dimensions = MediaDetails.getDimentions(
+ mContext, info.getAsInteger(MediaStore.MediaColumns.WIDTH),
+ info.getAsInteger(MediaStore.MediaColumns.HEIGHT));
+
+ mediaDetails.addDetail(MediaDetails.INDEX_DIMENSIONS, Dimensions);
+
+ mediaDetails.addDetail(MediaDetails.INDEX_TYPE,
+ info.get(MediaStore.Images.ImageColumns.MIME_TYPE));
+
+ String dateModified = DateUtils.formatDateTime(
+ mContext, info.getAsLong(MediaStore.Images.ImageColumns.DATE_TAKEN),
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE |
+ DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_ALL);
+ mediaDetails.addDetail(MediaDetails.INDEX_DATETIME, dateModified);
+
+ long mSizeInBytes = info.getAsLong(MediaStore.Images.ImageColumns.SIZE);
+ if (mSizeInBytes > 0) {
+ mediaDetails.addDetail(MediaDetails.INDEX_SIZE, mSizeInBytes);
+ }
+ }
+
+ return Optional.of(mediaDetails);
+ }
+
+ public static long getAvailableSpace() {
+ String state = Environment.getExternalStorageState();
+ Log.d(TAG, "External storage state=" + state);
+ if (Environment.MEDIA_CHECKING.equals(state)) {
+ return PREPARING;
+ }
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ return UNAVAILABLE;
+ }
+
+ File dir = createOutputmediaStorageDir();
+ if (dir == null)
+ return UNAVAILABLE;
+ if (!dir.isDirectory() || !dir.canWrite()) {
+ return UNAVAILABLE;
+ }
+
+ try {
+ StatFs stat = new StatFs(DIRECTORY);
+ return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
+ } catch (Exception e) {
+ Log.i(TAG, "Fail to access external storage", e);
+ }
+ return UNKNOWN_SIZE;
+ }
+}
diff --git a/camera/MultiCameraApplication/java/com/intel/multicamera/VideoRecord.java b/camera/MultiCameraApplication/java/com/intel/multicamera/VideoRecord.java
new file mode 100644
index 0000000..9da5b26
--- /dev/null
+++ b/camera/MultiCameraApplication/java/com/intel/multicamera/VideoRecord.java
@@ -0,0 +1,737 @@
+/*
+ * 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.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
+import android.net.Uri;
+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;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.intel.multicamera.SettingsPrefUtil.SIZE_HD;
+
+public class VideoRecord implements MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener{
+
+ private static final String TAG = "VideoRecord";
+ private CameraDevice mCameraDevice;
+ private AutoFitTextureView mTextureView;
+ private Activity mActivity;
+ private boolean mIsRecordingVideo;
+ private CameraCaptureSession cameraCaptureSessions;
+ private CaptureRequest.Builder captureRequestBuilder;
+ private SharedPreferences settings;
+ private MediaRecorder mMediaRecorder;
+ private ContentValues mCurrentVideoValues;
+ private String mVideoFilename, mCameraId;
+ private int mSensorOrientation;
+ private boolean mRecordingTimeCountsDown;
+ private Handler mBackgroundHandler;
+ private final Handler mHandler;
+ private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
+ private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
+ private static final int MSG_UPDATE_RECORD_TIME = 5;
+ private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
+ private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
+ private Size previewSize;
+ private long mRecordingStartTime;
+ private TextView mRecordingTimeView;
+ private String mVideoKey, mSettingsKey;
+
+ private CameraBase mCameraBase;
+ private MultiCamera ic_camera;
+ static final Size SIZE_720P = new Size(1280, 720);
+ static final Size SIZE_480P = new Size(640, 480);
+
+ public VideoRecord(CameraBase cameraBase, String videoKey, String cameraId,
+ AutoFitTextureView textureView, Activity activity, TextView recordingView,
+ String settingsKey) {
+ mCameraBase = cameraBase;
+ mTextureView = textureView;
+ mActivity = activity;
+ mVideoKey = videoKey;
+ mCameraId = cameraId;
+ mSettingsKey = settingsKey;
+ previewSize = SIZE_720P;
+ mRecordingTimeView = recordingView;
+ mRecordingTimeCountsDown = false;
+ mHandler = new MainHandler();
+ ic_camera = MultiCamera.getInstance();
+ }
+ /* Recording Start*/
+ public void startRecordingVideo() {
+ if (null == mCameraDevice || !mTextureView.isAvailable()) {
+ return;
+ }
+ MultiViewActivity.updateStorageSpace(new MultiViewActivity.OnStorageUpdateDoneListener() {
+ @Override
+ public void onStorageUpdateDone(long bytes) throws IOException, CameraAccessException {
+ if (bytes <= Utils.LOW_STORAGE_THRESHOLD_BYTES) {
+ Log.w(TAG, "Storage issue, ignore the start request");
+ Toast.makeText(mActivity, "LOW_STORAGE", Toast.LENGTH_SHORT).show();
+ } else {
+ CamcorderProfile mProfile;
+ mProfile = getSelectedCamorderProfile();
+ setUpMediaRecorder(mProfile);
+ startRecorder(mProfile);
+ }
+ }
+ });
+ }
+
+
+ // from MediaRecorder.OnErrorListener
+ @Override
+ public void onError(MediaRecorder mr, int what, int extra) {
+ Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
+ if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+ // We may have run out of space on the sdcard.
+ stopRecordingVideo();
+ MultiViewActivity.updateStorageSpace(null);
+ }
+ }
+
+
+ // from MediaRecorder.OnInfoListener
+ @Override
+ public void onInfo(MediaRecorder mr, int what, int extra) {
+ String[] VideofileDetails = new String[0];
+ if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING) {
+ Toast.makeText(mActivity, R.string.video_reach_size_limit, Toast.LENGTH_SHORT).show();
+
+ if (mIsRecordingVideo) {
+ saveVideo();
+
+ VideofileDetails = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO);
+ if (VideofileDetails == null || VideofileDetails.length < 5) {
+ Log.e(TAG, "setUpMediaRecorder Invalid file details");
+ return;
+ }
+
+ mVideoFilename = VideofileDetails[3];
+
+ CamcorderProfile mProfile = getSelectedCamorderProfile();
+
+ mCurrentVideoValues = Utils.getContentValues(
+ Utils.MEDIA_TYPE_VIDEO, VideofileDetails, mProfile.videoFrameWidth,
+ mProfile.videoFrameHeight, 0, 0);
+
+ try {
+ mMediaRecorder.setNextOutputFile(new File(VideofileDetails[3]));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ MultiViewActivity.updateStorageSpace(null);
+ }
+
+ public void stopRecordingVideo() {
+ mHandler.removeMessages(MSG_UPDATE_RECORD_TIME);
+ mIsRecordingVideo = false;
+
+ releaseMedia();
+
+ createCameraPreview();
+ saveVideo();
+ }
+
+
+ private Size getSelectedDimension(String Key) {
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+
+ Size mDimensions = SIZE_720P;
+
+ if (Key.compareTo(mVideoKey) == 0) {
+ CamcorderProfile mProfile;
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+
+ String videoQuality = settings.getString(mVideoKey, SIZE_HD);
+
+ int quality = SettingsPrefUtil.getFromSetting(videoQuality);
+
+ mProfile = CamcorderProfile.get(0, quality);
+
+ mDimensions = new Size(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
+
+ }
+
+ return mDimensions;
+ }
+
+ public void createCameraPreview() {
+ try {
+
+ closePreviewSession();
+
+ SurfaceTexture texture = mTextureView.getSurfaceTexture();
+ if (texture == null) return;
+
+ Surface surface = new Surface(texture);
+
+ String key = GetChnagedPrefKey();
+ if (key == null) return;
+
+ previewSize = getSelectedDimension(key);
+ if (previewSize == null) return;
+
+ if (previewSize.getWidth() == 640 || previewSize.getWidth() == 320) {
+ previewSize = SIZE_480P;
+
+ } else if (previewSize.getWidth() == 1280 || previewSize.getWidth() == 1920) {
+ previewSize = SIZE_720P;
+ }
+
+ Log.i(TAG, "Previewing with " + key + " " + previewSize.getWidth() + " x " +
+ previewSize.getHeight());
+
+ texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+
+ adjustAspectRatio(previewSize.getWidth(), previewSize.getHeight());
+
+ captureRequestBuilder =
+ mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ captureRequestBuilder.addTarget(surface);
+
+ mCameraDevice.createCaptureSession(
+ Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ // The camera is already closed
+ if (null == mCameraDevice) {
+ return;
+ }
+ // When the session is ready, we start displaying the preview.
+ cameraCaptureSessions = cameraCaptureSession;
+ updatePreview();
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
+ mCameraBase.closeCamera();
+ Toast.makeText(mActivity, " Preview Configuration change",
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ }, null);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void startRecorder(CamcorderProfile mProfile)
+ throws IOException, CameraAccessException {
+ closePreviewSession();
+
+ SurfaceTexture texture = mTextureView.getSurfaceTexture();
+ if (texture == null) return;
+
+ if (mProfile.videoFrameWidth == 1280 || mProfile.videoFrameHeight == 720) {
+ previewSize = SIZE_720P;
+
+ } else if (mProfile.videoFrameWidth == 640 || mProfile.videoFrameHeight == 320) {
+ previewSize = SIZE_480P;
+ }
+
+ Log.i(TAG,
+ "Video previewSize " + previewSize.getWidth() + " x " + previewSize.getHeight());
+
+ texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+
+ adjustAspectRatio(previewSize.getWidth(), previewSize.getHeight());
+
+ captureRequestBuilder = mCameraDevice.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
+ mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession camCaptureSession) {
+ cameraCaptureSessions = camCaptureSession;
+ updatePreview();
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // UI
+ mIsRecordingVideo = true;
+
+ // Start recording
+
+ mMediaRecorder.start();
+ Log.i(TAG, "MediaRecorder recording.... ");
+
+ mRecordingStartTime = SystemClock.uptimeMillis();
+
+ updateRecordingTime();
+ }
+ });
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ if (null != mActivity) {
+ Log.e(TAG, "Recording Failed");
+ Toast.makeText(mActivity, "Recording Failed", Toast.LENGTH_SHORT).show();
+ }
+
+ releaseMedia();
+ }
+ }, mBackgroundHandler);
+ }
+
+
+ public void closePreviewSession() {
+ if (cameraCaptureSessions != null) {
+ cameraCaptureSessions.close();
+ cameraCaptureSessions = null;
+ }
+ }
+
+
+ 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);
+ }
+
+
+ private void setRecordingTime(String text) {
+ mRecordingTimeView.setText(text);
+ }
+
+ public void setRecordingViewTime(TextView RecordingTimeView) {
+
+ mRecordingTimeView = RecordingTimeView;
+ }
+
+ private 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);
+ }
+ }
+
+ private void setUpMediaRecorder(CamcorderProfile mProfile) throws IOException {
+ if (null == mActivity) {
+ return;
+ }
+ String[] VideofileDetails;
+
+ mMediaRecorder = new MediaRecorder();
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+
+ {
+ Context mContext = mActivity.getApplicationContext();
+ AudioManager mAudioManager =
+ (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+ AudioDeviceInfo[] deviceList =
+ mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ for (AudioDeviceInfo audioDeviceInfo : deviceList) {
+ if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE) {
+ mMediaRecorder.setPreferredDevice(audioDeviceInfo);
+ break;
+ }
+ }
+ }
+
+ VideofileDetails = Utils.generateFileDetails(Utils.MEDIA_TYPE_VIDEO);
+ if (VideofileDetails == null || VideofileDetails.length < 5) {
+ Log.e(TAG, "setUpMediaRecorder Invalid file details");
+ return;
+ }
+
+ mCurrentVideoValues =
+ Utils.getContentValues(Utils.MEDIA_TYPE_VIDEO, VideofileDetails,
+ mProfile.videoFrameWidth, mProfile.videoFrameHeight, 0, 0);
+
+ mVideoFilename = VideofileDetails[3];
+
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+ /**
+ * set output file in media recorder
+ */
+ mMediaRecorder.setOutputFile(mVideoFilename);
+
+ mMediaRecorder.setVideoEncodingBitRate(10000000);
+
+ mMediaRecorder.setVideoFrameRate(mProfile.videoFrameRate);
+
+ Log.i(TAG, "MediaRecorder VideoSize " + mProfile.videoFrameWidth + " x " +
+ mProfile.videoFrameHeight);
+
+ mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
+ mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+
+ int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+ CameraManager manager = (CameraManager)mActivity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
+ mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ } catch (Exception e) {
+
+ }
+ 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;
+ }
+
+ // Set maximum file size.
+ long maxFileSize =
+ MultiViewActivity.getStorageSpaceBytes() - Utils.LOW_STORAGE_THRESHOLD_BYTES;
+ Log.d(TAG, "maximum file size is" + maxFileSize);
+ Toast.makeText(mActivity, "maxFileSize is " + maxFileSize, Toast.LENGTH_SHORT).show();
+ try {
+ mMediaRecorder.setMaxFileSize(maxFileSize);
+ } catch (RuntimeException exception) {
+ // We are going to ignore failure of setMaxFileSize here, as
+ // a) The composer selected may simply not support it, or
+ // b) The underlying media framework may not handle 64-bit range
+ // on the size restriction.
+ }
+
+ try {
+ mMediaRecorder.prepare();
+ } catch (IOException ex) {
+ Log.e(TAG, "MediaRecorder prepare failed for " + mVideoFilename, ex);
+ releaseMedia();
+ throw new RuntimeException(ex);
+ }
+
+ mMediaRecorder.setOnErrorListener(this);
+ mMediaRecorder.setOnInfoListener(this);
+ }
+
+
+ /**
+ * Sets the TextureView transform to preserve the aspect ratio of the video.
+ */
+ private void adjustAspectRatio(int videoWidth, int videoHeight) {
+ float megaPixels = previewSize.getWidth() * previewSize.getHeight() / 1000000f;
+ int numerator = SettingsPrefUtil.aspectRatioNumerator(previewSize);
+ int denominator = SettingsPrefUtil.aspectRatioDenominator(previewSize);
+ String AspectRatio =
+ mActivity.getString(R.string.metadata_dimensions_format, previewSize.getWidth(),
+ previewSize.getHeight(), megaPixels, numerator, denominator);
+ Log.i(TAG, "AspectRatio is " + AspectRatio);
+
+ if (videoWidth >= 1280 || videoHeight >= 720) {
+ configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
+ return;
+ }
+
+ int viewWidth = mTextureView.getWidth();
+ int viewHeight = mTextureView.getHeight();
+ double aspectRatio = (double)videoHeight / videoWidth;
+
+ int newWidth, newHeight;
+ if (viewHeight > (int)(viewWidth * aspectRatio)) {
+ // limited by narrow width; restrict height
+ newWidth = viewWidth;
+ newHeight = (int)(viewWidth * aspectRatio);
+ } else {
+ // limited by short height; restrict width
+ newWidth = (int)(viewHeight / aspectRatio);
+ newHeight = viewHeight;
+ }
+ int xoff = (viewWidth - newWidth) / 2;
+ int yoff = (viewHeight - newHeight) / 2;
+ Log.v(TAG, "video=" + videoWidth + "x" + videoHeight + " view=" + viewWidth + "x" +
+ viewHeight + " newView=" + newWidth + "x" + newHeight + " off=" + xoff +
+ "," + yoff);
+
+ Matrix txform = new Matrix();
+ mTextureView.getTransform(txform);
+ txform.setScale((float)newWidth / viewWidth, (float)newHeight / viewHeight);
+ // txform.postRotate(10); // just for fun
+ txform.postTranslate(xoff, yoff);
+ mTextureView.setTransform(txform);
+ }
+
+ private CamcorderProfile getSelectedCamorderProfile() {
+ CamcorderProfile mProfile;
+
+ settings = PreferenceManager.getDefaultSharedPreferences(mActivity);
+ String videoQuality = settings.getString(mVideoKey, SIZE_HD);
+
+ int quality = SettingsPrefUtil.getFromSetting(videoQuality);
+
+ Log.i(TAG, "Selected video quality " + videoQuality);
+
+ mProfile = CamcorderProfile.get(0, quality);
+
+ return mProfile;
+ }
+
+
+ public void releaseMedia() {
+ if (null != mMediaRecorder) {
+ try {
+ mMediaRecorder.setOnErrorListener(null);
+ mMediaRecorder.setOnInfoListener(null);
+ mMediaRecorder.stop();
+ Log.i(TAG, "MediaRecorder stop ");
+
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "stop fail", e);
+ if (mVideoFilename != null) {
+ deleteVideoFile(mVideoFilename);
+ }
+ }
+ mMediaRecorder.reset();
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ }
+ }
+
+
+ private void deleteVideoFile(String fileName) {
+ Log.v(TAG, "Deleting video " + fileName);
+ File f = new File(fileName);
+ if (!f.delete()) {
+ Log.e(TAG, "Could not delete " + fileName);
+ }
+ }
+
+
+ private void updatePreview() {
+ if (null == mCameraDevice) {
+ Log.e(TAG, "updatePreview error");
+ }
+ 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();
+ }
+ }
+
+
+ private void configureTransform(int viewWidth, int viewHeight) {
+ if (null == mTextureView || null == previewSize) {
+ return;
+ }
+ int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+ Matrix matrix = new Matrix();
+ RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
+ Log.v(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();
+ 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);
+ }
+ mTextureView.setTransform(matrix);
+ }
+
+
+ /**
+ * 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 String GetChnagedPrefKey() {
+ String Key = null;
+
+ switch (mSettingsKey) {
+ case "pref_resolution":
+ Key = getString(mSettingsKey, "capture_list");
+ break;
+ case "pref_resolution_1":
+ Key = getString(mSettingsKey, "capture_list_1");
+ break;
+ case "pref_resolution_2":
+ Key = getString(mSettingsKey, "capture_list_2");
+ break;
+ case "pref_resolution_3":
+ Key = getString(mSettingsKey, "capture_list_3");
+ break;
+ default:
+ break;
+ }
+
+ return Key;
+ }
+
+
+ /**
+ * Retrieve a setting's value as a String, manually specifiying
+ * a default value.
+ */
+ private 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;
+ }
+ }
+
+ private void saveVideo() {
+ long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
+ Log.w(TAG, "Video duration <= 0 : " + duration);
+ Uri uri;
+
+ mCurrentVideoValues.put(MediaStore.Video.Media.DURATION, duration);
+ mCurrentVideoValues.put(MediaStore.Video.Media.SIZE, new File(mVideoFilename).length());
+
+ uri = Utils.broadcastNewVideo(mActivity.getApplicationContext(), mCurrentVideoValues);
+
+ ic_camera.setCurrentUri(uri);
+
+ ic_camera.setCurrentFileInfo(mCurrentVideoValues);
+
+ Log.i(TAG, "video saved @: " + mVideoFilename);
+
+ mVideoFilename = null;
+ }
+
+ void setCameraDevice(CameraDevice cameraDevice) {
+ mCameraDevice = cameraDevice;
+ }
+}
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 ddb26ad..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/camera_switch.png b/camera/MultiCameraApplication/res/drawable/camera_switch.png
new file mode 100644
index 0000000..f675aca
Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/camera_switch.png differ
diff --git a/camera/MultiCameraApplication/res/drawable/detail.png b/camera/MultiCameraApplication/res/drawable/detail.png
new file mode 100644
index 0000000..b002707
Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/detail.png differ
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_camera_switch.png b/camera/MultiCameraApplication/res/drawable/ic_camera_switch.png
new file mode 100644
index 0000000..f180dc9
Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_camera_switch.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..af66d38
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_background.xml b/camera/MultiCameraApplication/res/drawable/ic_launcher_background.xml
index 3a37cf6..2408e30 100644
--- a/camera/MultiCameraApplication/res/drawable/ic_launcher_background.xml
+++ b/camera/MultiCameraApplication/res/drawable/ic_launcher_background.xml
@@ -1,170 +1,74 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 0000000..314de0f
--- /dev/null
+++ b/camera/MultiCameraApplication/res/drawable/ic_settings_black_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
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/ic_wall_moun_camera.png b/camera/MultiCameraApplication/res/drawable/ic_wall_moun_camera.png
new file mode 100644
index 0000000..ef3044f
Binary files /dev/null and b/camera/MultiCameraApplication/res/drawable/ic_wall_moun_camera.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_full_screen.xml b/camera/MultiCameraApplication/res/layout/activity_full_screen.xml
new file mode 100644
index 0000000..ebcc9d4
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/activity_full_screen.xml
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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_main.xml
deleted file mode 100644
index ee611bc..0000000
--- a/camera/MultiCameraApplication/res/layout/activity_main.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/camera/MultiCameraApplication/res/layout/activity_multiview.xml b/camera/MultiCameraApplication/res/layout/activity_multiview.xml
new file mode 100644
index 0000000..7651273
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/activity_multiview.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/activity_photo_preview.xml b/camera/MultiCameraApplication/res/layout/activity_photo_preview.xml
new file mode 100644
index 0000000..3c61107
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/activity_photo_preview.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/activity_video_record.xml b/camera/MultiCameraApplication/res/layout/activity_video_record.xml
new file mode 100644
index 0000000..c9c7612
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/activity_video_record.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/botmleftcam.xml b/camera/MultiCameraApplication/res/layout/botmleftcam.xml
new file mode 100644
index 0000000..9122cdf
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/botmleftcam.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/botmrightcam.xml b/camera/MultiCameraApplication/res/layout/botmrightcam.xml
new file mode 100644
index 0000000..9bd1ffc
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/botmrightcam.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/content_main.xml b/camera/MultiCameraApplication/res/layout/content_main.xml
new file mode 100644
index 0000000..c3b1627
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/content_main.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camera/MultiCameraApplication/res/layout/details.xml b/camera/MultiCameraApplication/res/layout/details.xml
new file mode 100644
index 0000000..f479b81
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/details.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/details_list.xml b/camera/MultiCameraApplication/res/layout/details_list.xml
new file mode 100644
index 0000000..b80ab6c
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/details_list.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/intentpreview.xml b/camera/MultiCameraApplication/res/layout/intentpreview.xml
new file mode 100644
index 0000000..21d0a5f
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/intentpreview.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/permissions.xml b/camera/MultiCameraApplication/res/layout/permissions.xml
new file mode 100644
index 0000000..46034e0
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/permissions.xml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/photopreview.xml b/camera/MultiCameraApplication/res/layout/photopreview.xml
new file mode 100644
index 0000000..225f01f
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/photopreview.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/settings_activity.xml b/camera/MultiCameraApplication/res/layout/settings_activity.xml
new file mode 100644
index 0000000..de6591a
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/settings_activity.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/camera/MultiCameraApplication/res/layout/topleftcam.xml b/camera/MultiCameraApplication/res/layout/topleftcam.xml
new file mode 100644
index 0000000..2c9e54f
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/topleftcam.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/layout/toprightcam.xml b/camera/MultiCameraApplication/res/layout/toprightcam.xml
new file mode 100644
index 0000000..2848020
--- /dev/null
+++ b/camera/MultiCameraApplication/res/layout/toprightcam.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher.xml b/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher.xml
index a26f6fb..c4a603d 100644
--- a/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher_round.xml b/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher_round.xml
index a26f6fb..c4a603d 100644
--- a/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/camera/MultiCameraApplication/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,5 @@
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher.png b/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher.png
index a2f5908..f792446 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher.png and b/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_foreground.png b/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..f83edab
Binary files /dev/null and b/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_round.png b/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_round.png
index 1b52399..7ab9eee 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_round.png and b/camera/MultiCameraApplication/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher.png b/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher.png
index ff10afd..07d954a 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher.png and b/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_foreground.png b/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..06a205b
Binary files /dev/null and b/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_round.png b/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_round.png
index 115a4c7..eb55f9b 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_round.png and b/camera/MultiCameraApplication/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher.png b/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher.png
index dcd3cd8..63a406a 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher.png and b/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_foreground.png b/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9ccf2b9
Binary files /dev/null and b/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_round.png b/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_round.png
index 459ca60..9fdaecb 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_round.png and b/camera/MultiCameraApplication/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher.png b/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher.png
index 8ca12fe..ef86631 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher.png and b/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_foreground.png b/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..7509a52
Binary files /dev/null and b/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_round.png b/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_round.png
index 8e19b41..a97bb5e 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_round.png and b/camera/MultiCameraApplication/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher.png b/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher.png
index b824ebd..a0d56cc 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher.png and b/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ced9426
Binary files /dev/null and b/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_round.png b/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_round.png
index 4c19a13..957cb28 100644
Binary files a/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_round.png and b/camera/MultiCameraApplication/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/camera/MultiCameraApplication/res/values/arrays.xml b/camera/MultiCameraApplication/res/values/arrays.xml
new file mode 100644
index 0000000..dd59803
--- /dev/null
+++ b/camera/MultiCameraApplication/res/values/arrays.xml
@@ -0,0 +1,149 @@
+
+
+
+ - 1920x1080
+ - 1280x720
+ - 640x480
+
+
+
+ - 1
+ - 2
+ - 3
+
+
+
+ - 1080p
+ - 720p
+ - 480p
+
+
+
+
+ - @string/pref_video_quality_entry_480p
+ - @string/pref_video_quality_entry_720p
+ - @string/pref_video_quality_entry_1080p
+ - @string/pref_video_quality_entry_2160p
+
+
+
+
+ - 1920x1080
+ - 1280x720
+ - 640x480
+
+
+
+ - 1
+ - 2
+ - 3
+
+
+
+ - 1080p
+ - 720p
+ - 480p
+
+
+
+ - @string/pref_video_quality_entry_480p
+ - @string/pref_video_quality_entry_720p
+ - @string/pref_video_quality_entry_1080p
+ - @string/pref_video_quality_entry_2160p
+
+
+
+
+ - 1920x1080
+ - 1280x720
+ - 640x480
+
+
+
+ - 1
+ - 2
+ - 3
+
+
+
+ - 1080p
+ - 720p
+ - 480p
+
+
+
+ - @string/pref_video_quality_entry_480p
+ - @string/pref_video_quality_entry_720p
+ - @string/pref_video_quality_entry_1080p
+ - @string/pref_video_quality_entry_2160p
+
+
+
+
+
+ - 1920x1080
+ - 1280x720
+ - 640x480
+
+
+
+ - 1
+ - 2
+ - 3
+
+
+
+ - 1080p
+ - 720p
+ - 480p
+
+
+
+ - @string/pref_video_quality_entry_480p
+ - @string/pref_video_quality_entry_720p
+ - @string/pref_video_quality_entry_1080p
+ - @string/pref_video_quality_entry_2160p
+
+
+
+
+
+ - One
+ - Two
+ - Three
+ - Four
+
+
+
+ - 1
+ - 2
+ - 3
+ - 4
+
+
+
+
+ - @string/pref_video_quality_entry_low
+ - @string/pref_video_quality_entry_high
+ - @string/pref_video_quality_entry_qcif
+ - @string/pref_video_quality_entry_cif
+ - @string/pref_video_quality_entry_480p
+ - @string/pref_video_quality_entry_720p
+ - @string/pref_video_quality_entry_1080p
+ - @string/pref_video_quality_entry_qvga
+ - @string/pref_video_quality_entry_2160p
+
+
+
+
+ - Reply
+ - Reply to all
+
+
+
+ - reply
+ - reply_all
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/values/colors.xml b/camera/MultiCameraApplication/res/values/colors.xml
index 2a12c47..61e5d14 100644
--- a/camera/MultiCameraApplication/res/values/colors.xml
+++ b/camera/MultiCameraApplication/res/values/colors.xml
@@ -1,6 +1,69 @@
-
-
- #3F51B5
- #303F9F
- #FF4081
-
+
+
+ #008577
+ #00574B
+ #D81B60
+ #333
+ #FFFFFFFF
+ #FFFF0033
+ #77333333
+ #FF33B5E5
+ #3F33B5E5
+ #FF000000
+ #DD777777
+ #FFC5C5C5
+ #40000000
+ #ff33b5e5
+ #ff282828
+ #FF2E2E2E
+ #FF33525E
+ #FF0099CC
+ #FFFF2222
+ #33B5E5
+ #ff33b5e5
+ #ff4c4c4c
+ #fff3f3f3
+ #ffffff00
+ #ffffffff
+ #90ffffff
+ #b0ffffff
+ #9000ff00
+ #90ff0000
+ #FFAAAAAA
+
+
+ #fff
+ #00000000
+ #e7e7e7
+ #ffffffff
+ #4c000000
+ #00000000
+ #26000000
+ #99000000
+
+ #4285f4
+ #db4437
+ #0f9d58
+ #ab47bc
+ #ff9e00
+ #00acc1
+
+ #262626
+ #4C000000
+
+ #1b1b1b
+
+ #4c000000
+
+ #33FFFFFF
+ #4CFFFFFF
+ @color/main_color_global
+ #FF76A7F9
+ @android:color/black
+
+ #2962FF
+
+ #2DFFFFFF
+
+ #00838F
+
diff --git a/camera/MultiCameraApplication/res/values/dimens.xml b/camera/MultiCameraApplication/res/values/dimens.xml
new file mode 100644
index 0000000..975b3c0
--- /dev/null
+++ b/camera/MultiCameraApplication/res/values/dimens.xml
@@ -0,0 +1,18 @@
+
+ 16dp
+
+ 56dp
+ 48dp
+ 60dp
+
+ 12dp
+ 48dp
+ 24dp
+ 54dp
+ 40dp
+ 70dp
+ 5dp
+ 1dp
+
+ 48dp
+
diff --git a/camera/MultiCameraApplication/res/values/ic_launcher_background.xml b/camera/MultiCameraApplication/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..09a756b
--- /dev/null
+++ b/camera/MultiCameraApplication/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #26A69A
+
\ No newline at end of file
diff --git a/camera/MultiCameraApplication/res/values/strings.xml b/camera/MultiCameraApplication/res/values/strings.xml
index 0fe6e28..4a2dfb3 100644
--- a/camera/MultiCameraApplication/res/values/strings.xml
+++ b/camera/MultiCameraApplication/res/values/strings.xml
@@ -1,3 +1,165 @@
-
- MultiCamera
-
+
+ MultiCamera
+ Settings
+ Settings
+ Record
+ Picture
+ Stop
+ Done
+
+
+
+
+ Low
+
+ High
+
+ QCIF
+
+ CIF
+
+ SD 480p
+
+ HD 720p
+
+ FHD 1080p
+
+ QVGA
+
+ UHD 4K
+
+
+
+ large
+
+ medium
+
+ small
+
+
+ 1280x720
+
+
+ Camera Source
+ Resolution & quality
+
+
+ Capture Resolution
+ Video Quality
+
+ %1$dx%2$d
+
+
+ USB Cameras
+ One
+
+
+ "'VID'_yyyyMMdd_HHmmss"
+
+
+ "'IMG'_yyyyMMdd_HHmmss"
+
+
+
+ Camera error
+
+
+ The app does not have critical permissions needed to run.
+ Please check your permissions settings.
+
+
+
+ Dismiss
+
+
+ READ ME
+
+ 1. Use ItsCameraIntentsTest OPTION for testing CAMERA INTENTS \n\n
+ 2. For MULTI CAMERA VIEW select 2nd option \n
+
+
+ CAMERA INTENTS TEST
+ MULTI CAMERA VIEW
+
+
+ Video recording has started.
+
+ Video recording has stopped.
+
+
+ Play Video
+
+
+ Can\'t play video.
+
+
+ Delete
+
+
+ %1$s megapixels
+
+
+ (%1$d:%2$d) %3$s megapixels
+
+
+ %1$02d:%2$02d
+
+ %1$d:%2$02d:%3$02d
+
+
+ Title
+
+ Description
+
+ Taken on
+
+ Location
+
+ Path
+
+ Width
+
+ Height
+ Dimensions
+
+ Orientation
+
+ Duration
+
+ MIME type
+
+ File size
+
+ Maker
+
+ Model
+
+ mm
+
+
+
+ Unknown
+
+ Details
+ Close
+
+
+
+
+
+ %1$d x
+ %2$d -
+ %3$,.1fMP (
+ %4$d:
+ %5$d )
+
+
+
+ Modified
+ Type
+ Size limit reached
+
+
+
diff --git a/camera/MultiCameraApplication/res/values/styles.xml b/camera/MultiCameraApplication/res/values/styles.xml
index efe449b..3f1c531 100644
--- a/camera/MultiCameraApplication/res/values/styles.xml
+++ b/camera/MultiCameraApplication/res/values/styles.xml
@@ -1,8 +1,51 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/xml/root_preferences.xml b/camera/MultiCameraApplication/res/xml/root_preferences.xml
new file mode 100644
index 0000000..83ba094
--- /dev/null
+++ b/camera/MultiCameraApplication/res/xml/root_preferences.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/xml/root_preferences_1.xml b/camera/MultiCameraApplication/res/xml/root_preferences_1.xml
new file mode 100644
index 0000000..7e3aca4
--- /dev/null
+++ b/camera/MultiCameraApplication/res/xml/root_preferences_1.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/xml/root_preferences_2.xml b/camera/MultiCameraApplication/res/xml/root_preferences_2.xml
new file mode 100644
index 0000000..c400f8b
--- /dev/null
+++ b/camera/MultiCameraApplication/res/xml/root_preferences_2.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/xml/root_preferences_3.xml b/camera/MultiCameraApplication/res/xml/root_preferences_3.xml
new file mode 100644
index 0000000..20900ac
--- /dev/null
+++ b/camera/MultiCameraApplication/res/xml/root_preferences_3.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/MultiCameraApplication/res/xml/settings.xml b/camera/MultiCameraApplication/res/xml/settings.xml
new file mode 100644
index 0000000..3428df0
--- /dev/null
+++ b/camera/MultiCameraApplication/res/xml/settings.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/camera/coding_style.txt b/camera/coding_style.txt
new file mode 100644
index 0000000..f96b56c
--- /dev/null
+++ b/camera/coding_style.txt
@@ -0,0 +1,4 @@
+# Before commiting any changes, run the following command to make sure proper coding style is followed
+# Use latest version of clang. Tested with clang version 8.0.0
+find . -regex '.*\.\(java\)' -exec clang-format -style=file -i {} \;
+