Skip to content

Commit

Permalink
Fix crash related to unsupported camera mode.
Browse files Browse the repository at this point in the history
Fixes #9106
  • Loading branch information
alex-signal authored and greyson-signal committed Oct 21, 2019
1 parent 097f97b commit 03dc220
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 2 deletions.
19 changes: 18 additions & 1 deletion src/org/thoughtcrime/securesms/mediasend/CameraXFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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;

Expand Down Expand Up @@ -236,7 +237,14 @@ public boolean onDoubleTap(MotionEvent e) {
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,
Expand All @@ -245,6 +253,9 @@ public boolean onDoubleTap(MotionEvent e) {
new CameraXVideoCaptureHelper.Callback() {
@Override
public void onVideoRecordStarted() {
if (camera.getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
camera.setCaptureMode(CameraXView.CaptureMode.VIDEO);
}
hideAndDisableControlsForVideoRecording(captureButton, flashButton, flipButton, outAnimation);
}

Expand Down Expand Up @@ -320,6 +331,12 @@ private void showAndEnableControlsAfterVideoRecording(@NonNull View captureButto
}

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -63,6 +64,7 @@ public void onError(@NonNull VideoCapture.VideoCaptureError videoCaptureError,
@NonNull String message,
@Nullable Throwable cause)
{
isRecording = false;
callback.onVideoError(cause);
Util.runOnMain(() -> resetCameraSizing());
}
Expand Down Expand Up @@ -202,7 +204,6 @@ public void onVideoCaptureComplete() {
Log.d(TAG, "onVideoCaptureComplete");
camera.stopRecording();


if (cameraMetricsAnimator != null && cameraMetricsAnimator.isRunning()) {
cameraMetricsAnimator.reverse();
}
Expand Down
79 changes: 79 additions & 0 deletions src/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
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;

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;
Expand All @@ -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 {
Expand Down Expand Up @@ -144,6 +168,61 @@ private static byte[] toJpegBytes(@NonNull Bitmap bitmap) throws IOException {
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;
Expand Down

0 comments on commit 03dc220

Please sign in to comment.