Upgrade CameraX to 1.1.0 and fork removal.

This commit is contained in:
Alex Hart
2022-07-27 10:53:04 -03:00
committed by Greyson Parrelli
parent e3e9f90094
commit a52b64281c
14 changed files with 151 additions and 1667 deletions

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.camera.view.video.ExperimentalVideo;
import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
@@ -18,7 +19,7 @@ public interface CameraFragment {
float PORTRAIT_ASPECT_RATIO = 9 / 16f;
@SuppressLint("RestrictedApi")
@SuppressLint({ "RestrictedApi", "UnsafeOptInUsageError" })
static Fragment newInstance() {
if (CameraXUtil.isSupported()) {
return CameraXFragment.newInstance();
@@ -27,7 +28,7 @@ public interface CameraFragment {
}
}
@SuppressLint("RestrictedApi")
@SuppressLint({ "RestrictedApi", "UnsafeOptInUsageError" })
static Fragment newInstanceForAvatarCapture() {
if (CameraXUtil.isSupported()) {
return CameraXFragment.newInstanceForAvatarCapture();

View File

@@ -5,8 +5,11 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.util.Rational;
import android.util.Size;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -18,7 +21,6 @@ import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -28,8 +30,10 @@ import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.ImageProxy;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.CameraController;
import androidx.camera.view.LifecycleCameraController;
import androidx.camera.view.PreviewView;
import androidx.camera.view.SignalCameraView;
import androidx.camera.view.video.ExperimentalVideo;
import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide;
@@ -64,18 +68,23 @@ import io.reactivex.rxjava3.disposables.Disposable;
* Camera captured implemented using the CameraX SDK, which uses Camera2 under the hood. Should be
* preferred whenever possible.
*/
@ExperimentalVideo
@RequiresApi(21)
public class CameraXFragment extends LoggingFragment implements CameraFragment {
private static final String TAG = Log.tag(CameraXFragment.class);
private static final String IS_VIDEO_ENABLED = "is_video_enabled";
private SignalCameraView camera;
private ViewGroup controlsContainer;
private Controller controller;
private View selfieFlash;
private MemoryFileDescriptor videoFileDescriptor;
private Disposable mostRecentItemDisposable = Disposable.disposed();
private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
private PreviewView previewView;
private ViewGroup controlsContainer;
private Controller controller;
private View selfieFlash;
private MemoryFileDescriptor videoFileDescriptor;
private LifecycleCameraController cameraController;
private Disposable mostRecentItemDisposable = Disposable.disposed();
private boolean isThumbAvailable;
private boolean isMediaSelected;
@@ -124,12 +133,18 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
ViewGroup cameraParent = view.findViewById(R.id.camerax_camera_parent);
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
this.camera = view.findViewById(R.id.camerax_camera);
this.previewView = view.findViewById(R.id.camerax_camera);
this.controlsContainer = view.findViewById(R.id.camerax_controls_container);
camera.setScaleType(PreviewView.ScaleType.FIT_CENTER);
camera.bindToLifecycle(getViewLifecycleOwner(), this::handleCameraInitializationError);
camera.setCameraLensFacing(CameraXUtil.toLensFacing(TextSecurePreferences.getDirectCaptureCameraId(requireContext())));
cameraController = new LifecycleCameraController(requireContext());
cameraController.bindToLifecycle(getViewLifecycleOwner());
cameraController.setCameraSelector(CameraXUtil.toCameraSelector(TextSecurePreferences.getDirectCaptureCameraId(requireContext())));
cameraController.setTapToFocusEnabled(true);
cameraController.setImageCaptureMode(CameraXUtil.getOptimalCaptureMode());
cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE);
previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
previewView.setController(cameraController);
onOrientationChanged(getResources().getConfiguration().orientation);
@@ -155,7 +170,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
public void onResume() {
super.onResume();
camera.bindToLifecycle(getViewLifecycleOwner(), this::handleCameraInitializationError);
cameraController.bindToLifecycle(getViewLifecycleOwner());
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
@@ -204,19 +219,17 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
});
}
private void handleCameraInitializationError(Throwable error) {
Log.w(TAG, "An error occurred", error);
Context context = getActivity();
if (context != null) {
Toast.makeText(context, R.string.CameraFragment__failed_to_open_camera, Toast.LENGTH_SHORT).show();
}
}
private void onOrientationChanged(int orientation) {
int layout = orientation == Configuration.ORIENTATION_PORTRAIT ? R.layout.camera_controls_portrait
: R.layout.camera_controls_landscape;
int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
Size size = CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, orientation == Configuration.ORIENTATION_PORTRAIT);
CameraController.OutputSize outputSize = new CameraController.OutputSize(size);
cameraController.setImageCaptureTargetSize(outputSize);
cameraController.setVideoCaptureTargetSize(new CameraController.OutputSize(VideoUtil.getVideoRecordingSize()));
controlsContainer.removeAllViews();
controlsContainer.addView(LayoutInflater.from(getContext()).inflate(layout, controlsContainer, false));
initControls();
@@ -301,15 +314,15 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
onCaptureClicked();
});
camera.setScaleType(PreviewView.ScaleType.FILL_CENTER);
previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
ProcessCameraProvider.getInstance(requireContext())
.addListener(() -> initializeFlipButton(flipButton, flashButton),
Executors.mainThreadExecutor());
flashButton.setAutoFlashEnabled(camera.hasFlash());
flashButton.setFlash(camera.getFlash());
flashButton.setOnFlashModeChangedListener(camera::setFlash);
flashButton.setAutoFlashEnabled(cameraController.getImageCaptureFlashMode() >= ImageCapture.FLASH_MODE_AUTO);
flashButton.setFlash(cameraController.getImageCaptureFlashMode());
flashButton.setOnFlashModeChangedListener(cameraController::setImageCaptureFlashMode);
galleryButton.setOnClickListener(v -> controller.onGalleryClicked());
countButton.setOnClickListener(v -> controller.onCameraCountButtonClicked());
@@ -322,8 +335,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
Animation inAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in);
Animation outAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out);
camera.setCaptureMode(SignalCameraView.CaptureMode.MIXED);
int maxDuration = VideoUtil.getMaxVideoRecordDurationInSeconds(requireContext(), controller.getMediaConstraints());
if (controller.getMaxVideoDuration() > 0) {
maxDuration = controller.getMaxVideoDuration();
@@ -334,7 +345,8 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper(
this,
captureButton,
camera,
cameraController,
previewView,
videoFileDescriptor,
maxDuration,
new CameraXVideoCaptureHelper.Callback() {
@@ -432,19 +444,21 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
CameraXSelfieFlashHelper flashHelper = new CameraXSelfieFlashHelper(
requireActivity().getWindow(),
camera,
cameraController,
selfieFlash
);
camera.takePicture(Executors.mainThreadExecutor(), new ImageCapture.OnImageCapturedCallback() {
cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE);
cameraController.takePicture(Executors.mainThreadExecutor(), new ImageCapture.OnImageCapturedCallback() {
@Override
public void onCaptureSuccess(@NonNull ImageProxy image) {
flashHelper.endFlash();
final boolean flip = cameraController.getCameraSelector() == CameraSelector.DEFAULT_FRONT_CAMERA;
SimpleTask.run(CameraXFragment.this.getViewLifecycleOwner().getLifecycle(), () -> {
stopwatch.split("captured");
try {
return CameraXUtil.toJpeg(image, camera.getCameraLensFacing() == CameraSelector.LENS_FACING_FRONT);
return CameraXUtil.toJpeg(image, flip);
} catch (IOException e) {
Log.w(TAG, "Failed to encode captured image.", e);
return null;
@@ -492,18 +506,20 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
return;
}
if (camera.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT) && camera.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)) {
if (cameraController.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) && cameraController.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)) {
flipButton.setVisibility(View.VISIBLE);
flipButton.setOnClickListener(v -> {
camera.toggleCamera();
TextSecurePreferences.setDirectCaptureCameraId(getContext(), CameraXUtil.toCameraDirectionInt(camera.getCameraLensFacing()));
cameraController.setCameraSelector(cameraController.getCameraSelector() == CameraSelector.DEFAULT_FRONT_CAMERA
? CameraSelector.DEFAULT_BACK_CAMERA
: CameraSelector.DEFAULT_FRONT_CAMERA);
TextSecurePreferences.setDirectCaptureCameraId(getContext(), CameraXUtil.toCameraDirectionInt(cameraController.getCameraSelector()));
Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(200);
animation.setInterpolator(new DecelerateInterpolator());
flipButton.startAnimation(animation);
flashButton.setAutoFlashEnabled(camera.hasFlash());
flashButton.setFlash(camera.getFlash());
flashButton.setAutoFlashEnabled(cameraController.getImageCaptureFlashMode() >= ImageCapture.FLASH_MODE_AUTO);
flashButton.setFlash(cameraController.getImageCaptureFlashMode());
});
GestureDetector gestureDetector = new GestureDetector(requireContext(), new GestureDetector.SimpleOnGestureListener() {
@@ -516,7 +532,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
}
});
camera.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
previewView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
} else {
flipButton.setVisibility(View.GONE);

View File

@@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.view.SignalCameraView;
import androidx.camera.view.CameraController;
@RequiresApi(21)
final class CameraXSelfieFlashHelper {
@@ -18,14 +18,14 @@ final class CameraXSelfieFlashHelper {
private static final long SELFIE_FLASH_DURATION_MS = 250;
private final Window window;
private final SignalCameraView camera;
private final CameraController camera;
private final View selfieFlash;
private float brightnessBeforeFlash;
private boolean inFlash;
CameraXSelfieFlashHelper(@NonNull Window window,
@NonNull SignalCameraView camera,
@NonNull CameraController camera,
@NonNull View selfieFlash)
{
this.window = window;
@@ -64,11 +64,9 @@ final class CameraXSelfieFlashHelper {
}
private boolean shouldUseViewBasedFlash() {
Integer cameraLensFacing = camera.getCameraLensFacing();
CameraSelector cameraSelector = camera.getCameraSelector() ;
return camera.getFlash() == ImageCapture.FLASH_MODE_ON &&
!camera.hasFlash() &&
cameraLensFacing != null &&
cameraLensFacing == CameraSelector.LENS_FACING_FRONT;
return camera.getImageCaptureFlashMode() == ImageCapture.FLASH_MODE_ON &&
cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA;
}
}

View File

@@ -13,8 +13,13 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.VideoCapture;
import androidx.camera.view.SignalCameraView;
import androidx.camera.core.ZoomState;
import androidx.camera.view.CameraController;
import androidx.camera.view.PreviewView;
import androidx.camera.view.video.ExperimentalVideo;
import androidx.camera.view.video.OnVideoSavedCallback;
import androidx.camera.view.video.OutputFileOptions;
import androidx.camera.view.video.OutputFileResults;
import androidx.fragment.app.Fragment;
import com.bumptech.glide.util.Executors;
@@ -28,9 +33,11 @@ import org.thoughtcrime.securesms.video.VideoUtil;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@RequiresApi(26)
@ExperimentalVideo
class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener {
private static final String TAG = CameraXVideoCaptureHelper.class.getName();
@@ -38,23 +45,22 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
private static final long VIDEO_SIZE = 10 * 1024 * 1024;
private final @NonNull Fragment fragment;
private final @NonNull SignalCameraView camera;
private final @NonNull PreviewView previewView;
private final @NonNull CameraController cameraController;
private final @NonNull Callback callback;
private final @NonNull MemoryFileDescriptor memoryFileDescriptor;
private final @NonNull ValueAnimator updateProgressAnimator;
private final @NonNull Debouncer debouncer;
private boolean isRecording;
private ValueAnimator cameraMetricsAnimator;
private final VideoCapture.OnVideoSavedCallback videoSavedListener = new VideoCapture.OnVideoSavedCallback() {
private final OnVideoSavedCallback videoSavedListener = new OnVideoSavedCallback() {
@SuppressLint("RestrictedApi")
@Override
public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {
public void onVideoSaved(@NonNull OutputFileResults outputFileResults) {
try {
isRecording = false;
debouncer.clear();
camera.setZoomRatio(camera.getMinZoomRatio());
cameraController.setZoomRatio(Objects.requireNonNull(cameraController.getZoomState().getValue()).getMinZoomRatio());
memoryFileDescriptor.seek(0);
callback.onVideoSaved(memoryFileDescriptor.getFileDescriptor());
} catch (IOException e) {
@@ -65,7 +71,6 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
@SuppressLint("RestrictedApi")
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
isRecording = false;
debouncer.clear();
callback.onVideoError(cause);
}
@@ -73,13 +78,15 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
CameraXVideoCaptureHelper(@NonNull Fragment fragment,
@NonNull CameraButtonView captureButton,
@NonNull SignalCameraView camera,
@NonNull CameraController cameraController,
@NonNull PreviewView previewView,
@NonNull MemoryFileDescriptor memoryFileDescriptor,
int maxVideoDurationSec,
@NonNull Callback callback)
{
this.fragment = fragment;
this.camera = camera;
this.cameraController = cameraController;
this.previewView = previewView;
this.memoryFileDescriptor = memoryFileDescriptor;
this.callback = callback;
this.updateProgressAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(TimeUnit.SECONDS.toMillis(maxVideoDurationSec));
@@ -94,7 +101,6 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
Log.d(TAG, "onVideoCaptureStarted");
if (canRecordAudio()) {
isRecording = true;
beginCameraRecording();
} else {
displayAudioRecordingPermissionsDialog();
@@ -117,13 +123,14 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
@SuppressLint("RestrictedApi")
private void beginCameraRecording() {
this.camera.setZoomRatio(this.camera.getMinZoomRatio());
this.cameraController.setZoomRatio(Objects.requireNonNull(this.cameraController.getZoomState().getValue()).getMinZoomRatio());
callback.onVideoRecordStarted();
shrinkCaptureArea();
VideoCapture.OutputFileOptions options = new VideoCapture.OutputFileOptions.Builder(memoryFileDescriptor.getFileDescriptor()).build();
OutputFileOptions options = OutputFileOptions.builder(memoryFileDescriptor.getParcelFileDescriptor()).build();
camera.startRecording(options, Executors.mainThreadExecutor(), videoSavedListener);
cameraController.setEnabledUseCases(CameraController.VIDEO_CAPTURE);
cameraController.startRecording(options, Executors.mainThreadExecutor(), videoSavedListener);
updateProgressAnimator.start();
debouncer.publish(this::onVideoCaptureComplete);
}
@@ -152,7 +159,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
cameraMetricsAnimator = ValueAnimator.ofFloat(screenSize.getWidth(), targetWidthForAnimation);
}
ViewGroup.LayoutParams params = camera.getLayoutParams();
ViewGroup.LayoutParams params = previewView.getLayoutParams();
cameraMetricsAnimator.setInterpolator(new LinearInterpolator());
cameraMetricsAnimator.setDuration(200);
cameraMetricsAnimator.addUpdateListener(animation -> {
@@ -161,13 +168,13 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
} else {
params.width = Math.round((float) animation.getAnimatedValue());
}
camera.setLayoutParams(params);
previewView.setLayoutParams(params);
});
cameraMetricsAnimator.start();
}
private Size getScreenSize() {
DisplayMetrics metrics = camera.getResources().getDisplayMetrics();
DisplayMetrics metrics = previewView.getResources().getDisplayMetrics();
return new Size(metrics.widthPixels, metrics.heightPixels);
}
@@ -179,11 +186,11 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
@Override
public void onVideoCaptureComplete() {
isRecording = false;
if (!canRecordAudio()) return;
Log.d(TAG, "onVideoCaptureComplete");
camera.stopRecording();
cameraController.stopRecording();
cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE);
if (cameraMetricsAnimator != null && cameraMetricsAnimator.isRunning()) {
cameraMetricsAnimator.reverse();
@@ -195,8 +202,9 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
@Override
public void onZoomIncremented(float increment) {
float range = camera.getMaxZoomRatio() - camera.getMinZoomRatio();
camera.setZoomRatio((range * increment) + camera.getMinZoomRatio());
ZoomState zoomState = Objects.requireNonNull(cameraController.getZoomState().getValue());
float range = zoomState.getMaxZoomRatio() - zoomState.getMinZoomRatio();
cameraController.setZoomRatio((range * increment) + zoomState.getMinZoomRatio());
}
static MemoryFileDescriptor createFileDescriptor(@NonNull Context context) throws MemoryFileDescriptor.MemoryFileException {

View File

@@ -6,6 +6,7 @@ import android.os.Parcelable;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.camera.core.ImageCapture;
@@ -14,6 +15,7 @@ import org.thoughtcrime.securesms.R;
import java.util.Arrays;
import java.util.List;
@RequiresApi(21)
public final class CameraXFlashToggleView extends AppCompatImageView {
private static final String STATE_FLASH_INDEX = "flash.toggle.state.flash.index";

View File

@@ -109,23 +109,26 @@ public class CameraXUtil {
return Build.VERSION.SDK_INT >= 21 && !CameraXModelBlacklist.isBlacklisted();
}
public static int toCameraDirectionInt(int facing) {
if (facing == CameraSelector.LENS_FACING_FRONT) {
@RequiresApi(21)
public static int toCameraDirectionInt(CameraSelector cameraSelector) {
if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) {
return Camera.CameraInfo.CAMERA_FACING_FRONT;
} else {
return Camera.CameraInfo.CAMERA_FACING_BACK;
}
}
public static int toLensFacing(@CameraSelector.LensFacing int cameraDirectionInt) {
@RequiresApi(21)
public static CameraSelector toCameraSelector(@CameraSelector.LensFacing int cameraDirectionInt) {
if (cameraDirectionInt == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return CameraSelector.LENS_FACING_FRONT;
return CameraSelector.DEFAULT_FRONT_CAMERA;
} else {
return CameraSelector.LENS_FACING_BACK;
return CameraSelector.DEFAULT_BACK_CAMERA;
}
}
public static @NonNull @ImageCapture.CaptureMode int getOptimalCaptureMode() {
@RequiresApi(21)
public static @ImageCapture.CaptureMode int getOptimalCaptureMode() {
return FastCameraModels.contains(Build.MODEL) ? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
: ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY;
}

View File

@@ -169,6 +169,10 @@ public final class MemoryFileDescriptor implements Closeable {
return parcelFileDescriptor.getFileDescriptor();
}
public ParcelFileDescriptor getParcelFileDescriptor() {
return parcelFileDescriptor;
}
public void seek(long position) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(getFileDescriptor())) {
fileInputStream.getChannel().position(position);