Fix crash related to unsupported camera mode.

Fixes #9106
This commit is contained in:
Alex Hart 2019-10-21 18:54:06 -03:00 committed by Greyson Parrelli
parent 097f97b5e4
commit 03dc220cea
3 changed files with 99 additions and 2 deletions

View File

@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional; 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 inAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in);
Animation outAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out); Animation outAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out);
if (CameraXUtil.isMixedModeSupported(requireContext())) {
Log.i(TAG, "Device supports mixed mode recording. [" + CameraXUtil.getLowestSupportedHardwareLevel(requireContext()) + "]");
camera.setCaptureMode(CameraXView.CaptureMode.MIXED); 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( captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper(
this, this,
captureButton, captureButton,
@ -245,6 +253,9 @@ public class CameraXFragment extends Fragment implements CameraFragment {
new CameraXVideoCaptureHelper.Callback() { new CameraXVideoCaptureHelper.Callback() {
@Override @Override
public void onVideoRecordStarted() { public void onVideoRecordStarted() {
if (camera.getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
camera.setCaptureMode(CameraXView.CaptureMode.VIDEO);
}
hideAndDisableControlsForVideoRecording(captureButton, flashButton, flipButton, outAnimation); hideAndDisableControlsForVideoRecording(captureButton, flashButton, flipButton, outAnimation);
} }
@ -320,6 +331,12 @@ public class CameraXFragment extends Fragment implements CameraFragment {
} }
private void onCaptureClicked() { 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"); Stopwatch stopwatch = new Stopwatch("Capture");
CameraXSelfieFlashHelper flashHelper = new CameraXSelfieFlashHelper( CameraXSelfieFlashHelper flashHelper = new CameraXSelfieFlashHelper(

View File

@ -50,6 +50,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
@Override @Override
public void onVideoSaved(@NonNull FileDescriptor fileDescriptor) { public void onVideoSaved(@NonNull FileDescriptor fileDescriptor) {
try { try {
isRecording = false;
camera.setZoomLevel(0f); camera.setZoomLevel(0f);
memoryFileDescriptor.seek(0); memoryFileDescriptor.seek(0);
callback.onVideoSaved(fileDescriptor); callback.onVideoSaved(fileDescriptor);
@ -63,6 +64,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
@NonNull String message, @NonNull String message,
@Nullable Throwable cause) @Nullable Throwable cause)
{ {
isRecording = false;
callback.onVideoError(cause); callback.onVideoError(cause);
Util.runOnMain(() -> resetCameraSizing()); Util.runOnMain(() -> resetCameraSizing());
} }
@ -202,7 +204,6 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
Log.d(TAG, "onVideoCaptureComplete"); Log.d(TAG, "onVideoCaptureComplete");
camera.stopRecording(); camera.stopRecording();
if (cameraMetricsAnimator != null && cameraMetricsAnimator.isRunning()) { if (cameraMetricsAnimator != null && cameraMetricsAnimator.isRunning()) {
cameraMetricsAnimator.reverse(); cameraMetricsAnimator.reverse();
} }

View File

@ -1,12 +1,17 @@
package org.thoughtcrime.securesms.mediasend.camerax; package org.thoughtcrime.securesms.mediasend.camerax;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder; import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.hardware.Camera; 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.os.Build;
import android.util.Rational; import android.util.Rational;
import android.util.Size; import android.util.Size;
@ -14,6 +19,7 @@ import android.util.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.camera.camera2.impl.compat.CameraManagerCompat;
import androidx.camera.core.CameraX; import androidx.camera.core.CameraX;
import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageProxy; import androidx.camera.core.ImageProxy;
@ -29,6 +35,24 @@ public class CameraXUtil {
private static final String TAG = Log.tag(CameraXUtil.class); 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") @SuppressWarnings("SuspiciousNameCombination")
@RequiresApi(21) @RequiresApi(21)
public static ImageResult toJpeg(@NonNull ImageProxy image, int rotation, boolean flip) throws IOException { public static ImageResult toJpeg(@NonNull ImageProxy image, int rotation, boolean flip) throws IOException {
@ -144,6 +168,61 @@ public class CameraXUtil {
return out.toByteArray(); 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 { public static class ImageResult {
private final byte[] data; private final byte[] data;
private final int width; private final int width;