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 {} \; +