From 03dc220cead33a162fc1addd3225fad2253f44dc Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 21 Oct 2019 18:54:06 -0300 Subject: [PATCH] Fix crash related to unsupported camera mode. Fixes #9106 --- .../securesms/mediasend/CameraXFragment.java | 19 ++++- .../mediasend/CameraXVideoCaptureHelper.java | 3 +- .../mediasend/camerax/CameraXUtil.java | 79 +++++++++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/src/org/thoughtcrime/securesms/mediasend/CameraXFragment.java index 49b7f52d3e..8fef881535 100644 --- a/src/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/src/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.util.MemoryFileDescriptor; import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ThemeUtil; +import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.whispersystems.libsignal.util.guava.Optional; @@ -236,7 +237,14 @@ public class CameraXFragment extends Fragment implements CameraFragment { Animation inAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in); Animation outAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out); - camera.setCaptureMode(CameraXView.CaptureMode.MIXED); + if (CameraXUtil.isMixedModeSupported(requireContext())) { + Log.i(TAG, "Device supports mixed mode recording. [" + CameraXUtil.getLowestSupportedHardwareLevel(requireContext()) + "]"); + camera.setCaptureMode(CameraXView.CaptureMode.MIXED); + } else { + Log.i(TAG, "Device does not support mixed mode recording, falling back to IMAGE [" + CameraXUtil.getLowestSupportedHardwareLevel(requireContext()) + "]"); + camera.setCaptureMode(CameraXView.CaptureMode.IMAGE); + } + captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper( this, captureButton, @@ -245,6 +253,9 @@ public class CameraXFragment extends Fragment implements CameraFragment { new CameraXVideoCaptureHelper.Callback() { @Override public void onVideoRecordStarted() { + if (camera.getCaptureMode() == CameraXView.CaptureMode.IMAGE) { + camera.setCaptureMode(CameraXView.CaptureMode.VIDEO); + } hideAndDisableControlsForVideoRecording(captureButton, flashButton, flipButton, outAnimation); } @@ -320,6 +331,12 @@ public class CameraXFragment extends Fragment implements CameraFragment { } private void onCaptureClicked() { + if (camera.getCaptureMode() == CameraXView.CaptureMode.VIDEO) { + camera.setCaptureMode(CameraXView.CaptureMode.IMAGE); + Util.runOnMainDelayed(this::onCaptureClicked, 100); + return; + } + Stopwatch stopwatch = new Stopwatch("Capture"); CameraXSelfieFlashHelper flashHelper = new CameraXSelfieFlashHelper( diff --git a/src/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java b/src/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java index 4a266f325b..6b7b646667 100644 --- a/src/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java +++ b/src/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java @@ -50,6 +50,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener @Override public void onVideoSaved(@NonNull FileDescriptor fileDescriptor) { try { + isRecording = false; camera.setZoomLevel(0f); memoryFileDescriptor.seek(0); callback.onVideoSaved(fileDescriptor); @@ -63,6 +64,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener @NonNull String message, @Nullable Throwable cause) { + isRecording = false; callback.onVideoError(cause); Util.runOnMain(() -> resetCameraSizing()); } @@ -202,7 +204,6 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener Log.d(TAG, "onVideoCaptureComplete"); camera.stopRecording(); - if (cameraMetricsAnimator != null && cameraMetricsAnimator.isRunning()) { cameraMetricsAnimator.reverse(); } diff --git a/src/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java b/src/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java index ed6c2cd17a..32277c03ba 100644 --- a/src/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java +++ b/src/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java @@ -1,12 +1,17 @@ package org.thoughtcrime.securesms.mediasend.camerax; import android.annotation.TargetApi; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; import android.hardware.Camera; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CameraMetadata; import android.os.Build; import android.util.Rational; import android.util.Size; @@ -14,6 +19,7 @@ import android.util.Size; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.camera.camera2.impl.compat.CameraManagerCompat; import androidx.camera.core.CameraX; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageProxy; @@ -29,6 +35,24 @@ public class CameraXUtil { private static final String TAG = Log.tag(CameraXUtil.class); + @RequiresApi(21) + private static final int[] CAMERA_HARDWARE_LEVEL_ORDERING = new int[]{CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL}; + + @RequiresApi(24) + private static final int[] CAMERA_HARDWARE_LEVEL_ORDERING_24 = new int[]{CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3}; + + @RequiresApi(28) + private static final int[] CAMERA_HARDWARE_LEVEL_ORDERING_28 = new int[]{CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, + CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3}; + @SuppressWarnings("SuspiciousNameCombination") @RequiresApi(21) public static ImageResult toJpeg(@NonNull ImageProxy image, int rotation, boolean flip) throws IOException { @@ -144,6 +168,61 @@ public class CameraXUtil { return out.toByteArray(); } + @RequiresApi(21) + public static boolean isMixedModeSupported(@NonNull Context context) { + return getLowestSupportedHardwareLevel(context) != CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + } + + @RequiresApi(21) + public static int getLowestSupportedHardwareLevel(@NonNull Context context) { + CameraManager cameraManager = CameraManagerCompat.from(context).unwrap(); + + try { + int supported = maxHardwareLevel(); + + for (String cameraId : cameraManager.getCameraIdList()) { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); + Integer hwLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); + + if (hwLevel == null || hwLevel == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { + return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + } + + supported = smallerHardwareLevel(supported, hwLevel); + } + + return supported; + } catch (CameraAccessException e) { + Log.w(TAG, "Failed to enumerate cameras", e); + + return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + } + } + + @RequiresApi(21) + private static int maxHardwareLevel() { + if (Build.VERSION.SDK_INT >= 24) return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3; + else return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL; + } + + @RequiresApi(21) + private static int smallerHardwareLevel(int levelA, int levelB) { + + int[] hardwareInfoOrdering = getHardwareInfoOrdering(); + for (int hwInfo : hardwareInfoOrdering) { + if (levelA == hwInfo || levelB == hwInfo) return hwInfo; + } + + return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; + } + + @RequiresApi(21) + private static int[] getHardwareInfoOrdering() { + if (Build.VERSION.SDK_INT >= 28) return CAMERA_HARDWARE_LEVEL_ORDERING_28; + else if (Build.VERSION.SDK_INT >= 24) return CAMERA_HARDWARE_LEVEL_ORDERING_24; + else return CAMERA_HARDWARE_LEVEL_ORDERING; + } + public static class ImageResult { private final byte[] data; private final int width;