diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java index 6368efbac3..4ebd58372c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -6,16 +6,12 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.res.Resources; import android.graphics.Color; 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; -import android.view.OrientationEventListener; import android.view.Surface; import android.view.View; import android.view.ViewGroup; @@ -29,10 +25,16 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.camera.core.AspectRatio; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; import androidx.camera.core.ImageProxy; +import androidx.camera.video.FallbackStrategy; +import androidx.camera.video.Quality; +import androidx.camera.video.QualitySelector; +import androidx.camera.view.CameraController; +import androidx.camera.view.LifecycleCameraController; import androidx.camera.view.PreviewView; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; @@ -41,6 +43,7 @@ import androidx.core.content.ContextCompat; import com.bumptech.glide.Glide; import com.google.android.material.button.MaterialButton; import com.google.android.material.card.MaterialCardView; +import com.google.common.util.concurrent.ListenableFuture; import org.signal.core.util.Stopwatch; import org.signal.core.util.concurrent.SimpleTask; @@ -50,19 +53,15 @@ import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.animation.AnimationCompleteListener; import org.thoughtcrime.securesms.components.TooltipPopup; -import org.thoughtcrime.securesms.mediasend.camerax.CameraXController; import org.thoughtcrime.securesms.mediasend.camerax.CameraXFlashToggleView; import org.thoughtcrime.securesms.mediasend.camerax.CameraXModePolicy; import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil; -import org.thoughtcrime.securesms.mediasend.camerax.PlatformCameraController; -import org.thoughtcrime.securesms.mediasend.camerax.SignalCameraController; import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations; import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.BottomSheetUtil; -import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.MemoryFileDescriptor; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; @@ -76,7 +75,6 @@ import java.util.concurrent.Executors; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.Disposable; -import kotlin.Unit; import static org.thoughtcrime.securesms.permissions.PermissionDeniedBottomSheet.showPermissionFragment; @@ -91,7 +89,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { private static final String IS_QR_SCAN_ENABLED = "is_qr_scan_enabled"; - private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9); private static final PreviewView.ScaleType PREVIEW_SCALE_TYPE = PreviewView.ScaleType.FILL_CENTER; private PreviewView previewView; @@ -100,8 +97,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { private Controller controller; private View selfieFlash; private MemoryFileDescriptor videoFileDescriptor; - private CameraXController cameraController; - private CameraXOrientationListener orientationListener; + private LifecycleCameraController cameraController; private Disposable mostRecentItemDisposable = Disposable.disposed(); private CameraXModePolicy cameraXModePolicy; private CameraScreenBrightnessController cameraScreenBrightnessController; @@ -148,8 +144,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { if (controller == null) { throw new IllegalStateException("Parent must implement controller interface."); } - - this.orientationListener = new CameraXOrientationListener(context); } @Override @@ -178,62 +172,49 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { previewView.setScaleType(PREVIEW_SCALE_TYPE); - if (RemoteConfig.customCameraXController()) { - View focusIndicator = view.findViewById(R.id.camerax_focus_indicator); - cameraController = new SignalCameraController(requireContext(), getViewLifecycleOwner(), previewView, focusIndicator); - } else { - PlatformCameraController platformController = new PlatformCameraController(requireContext()); - platformController.initializeAndBind(requireContext(), getViewLifecycleOwner()); - previewView.setController(platformController.getDelegate()); - cameraController = platformController; - } - cameraXModePolicy.initialize(cameraController); + final LifecycleCameraController lifecycleCameraController = new LifecycleCameraController(requireContext()); + cameraController = lifecycleCameraController; + lifecycleCameraController.bindToLifecycle(getViewLifecycleOwner()); + lifecycleCameraController.setCameraSelector(CameraXUtil.toCameraSelector(TextSecurePreferences.getDirectCaptureCameraId(requireContext()))); + lifecycleCameraController.setTapToFocusEnabled(true); + lifecycleCameraController.setImageCaptureMode(CameraXUtil.getOptimalCaptureMode()); + lifecycleCameraController.setVideoCaptureQualitySelector(QualitySelector.from(Quality.HD, FallbackStrategy.lowerQualityThan(Quality.HD))); + previewView.setController(lifecycleCameraController); + cameraXModePolicy.initialize(lifecycleCameraController); cameraScreenBrightnessController = new CameraScreenBrightnessController( requireActivity().getWindow(), - new CameraStateProvider(cameraController) + new CameraStateProvider(lifecycleCameraController) ); previewView.setScaleType(PREVIEW_SCALE_TYPE); - onOrientationChanged(); + lifecycleCameraController.setImageCaptureTargetSize(new CameraController.OutputSize(AspectRatio.RATIO_16_9)); - if (RemoteConfig.customCameraXController()) { - cameraController.initializeAndBind(requireContext(), getViewLifecycleOwner()); - } + controlsContainer.removeAllViews(); + controlsContainer.addView(LayoutInflater.from(getContext()).inflate(R.layout.camera_controls_portrait, controlsContainer, false)); + + initControls(lifecycleCameraController); if (requireArguments().getBoolean(IS_QR_SCAN_ENABLED, false)) { - cameraController.setImageAnalysisAnalyzer(qrAnalysisExecutor, imageProxy -> { - try { + lifecycleCameraController.setImageAnalysisAnalyzer(qrAnalysisExecutor, imageProxy -> { + try (imageProxy) { String data = qrProcessor.getScannedData(imageProxy); if (data != null) { controller.onQrCodeFound(data); } - } finally { - imageProxy.close(); } }); } } - @Override - public void onStart() { - super.onStart(); - orientationListener.enable(); - } - - @Override - public void onStop() { - super.onStop(); - orientationListener.disable(); - } - @Override public void onResume() { super.onResume(); - cameraController.bindToLifecycle(getViewLifecycleOwner(), () -> Log.d(TAG, "Camera init complete from onResume")); + cameraController.bindToLifecycle(getViewLifecycleOwner()); + Log.d(TAG, "Camera init complete from onResume"); requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); if (hasCameraPermission()) { missingPermissionsContainer.setVisibility(View.GONE); @@ -334,19 +315,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { return Permissions.hasAll(requireContext(), Manifest.permission.CAMERA); } - private void onOrientationChanged() { - int layout = R.layout.camera_controls_portrait; - - int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels); - Size size = CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, true); - - cameraController.setImageCaptureTargetSize(size); - - controlsContainer.removeAllViews(); - controlsContainer.addView(LayoutInflater.from(getContext()).inflate(layout, controlsContainer, false)); - initControls(); - } - private void presentRecentItemThumbnail(@Nullable Media media) { View thumbBackground = controlsContainer.findViewById(R.id.camera_gallery_button_background); ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button); @@ -414,7 +382,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } @SuppressLint({ "ClickableViewAccessibility", "MissingPermission" }) - private void initControls() { + private void initControls(LifecycleCameraController lifecycleCameraController) { View flipButton = requireView().findViewById(R.id.camera_flip_button); CameraButtonView captureButton = requireView().findViewById(R.id.camera_capture_button); View galleryButton = requireView().findViewById(R.id.camera_gallery_button); @@ -430,8 +398,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { selfieFlash = requireView().findViewById(R.id.camera_selfie_flash); + final ListenableFuture cameraInitFuture = lifecycleCameraController.getInitializationFuture(); captureButton.setOnClickListener(v -> { - if (hasCameraPermission() && cameraController.isInitialized()) { + if (hasCameraPermission() && cameraInitFuture.isDone()) { captureButton.setEnabled(false); flipButton.setEnabled(false); flashButton.setEnabled(false); @@ -443,10 +412,10 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { previewView.setScaleType(PREVIEW_SCALE_TYPE); - cameraController.addInitializationCompletedListener(ContextCompat.getMainExecutor(requireContext()), () -> initializeFlipButton(flipButton, flashButton)); + cameraInitFuture.addListener(() -> initializeFlipButton(flipButton, flashButton), ContextCompat.getMainExecutor(requireContext())); - flashButton.setAutoFlashEnabled(cameraController.getImageCaptureFlashMode() >= ImageCapture.FLASH_MODE_AUTO); - flashButton.setFlash(cameraController.getImageCaptureFlashMode()); + flashButton.setAutoFlashEnabled(lifecycleCameraController.getImageCaptureFlashMode() >= ImageCapture.FLASH_MODE_AUTO); + flashButton.setFlash(lifecycleCameraController.getImageCaptureFlashMode()); flashButton.setOnFlashModeChangedListener(mode -> { cameraController.setImageCaptureFlashMode(mode); cameraScreenBrightnessController.onCameraFlashChanged(mode == ImageCapture.FLASH_MODE_ON); @@ -473,7 +442,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper( this, captureButton, - cameraController, + lifecycleCameraController, previewView, videoFileDescriptor, cameraXModePolicy, @@ -630,14 +599,14 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } @SuppressLint({ "MissingPermission" }) - private Unit initializeFlipButton(@NonNull View flipButton, @NonNull CameraXFlashToggleView flashButton) { + private void initializeFlipButton(@NonNull View flipButton, @NonNull CameraXFlashToggleView flashButton) { if (getContext() == null) { Log.w(TAG, "initializeFlipButton called either before or after fragment was attached."); - return Unit.INSTANCE; + return; } if (!getLifecycle().getCurrentState().isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED)) { - return Unit.INSTANCE; + return; } getViewLifecycleOwner().getLifecycle().addObserver(cameraScreenBrightnessController); @@ -661,7 +630,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { GestureDetector gestureDetector = new GestureDetector(requireContext(), new GestureDetector.SimpleOnGestureListener() { @Override - public boolean onDoubleTap(MotionEvent e) { + public boolean onDoubleTap(@NonNull MotionEvent e) { if (flipButton.isEnabled()) { flipButton.performClick(); } @@ -674,14 +643,13 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } else { flipButton.setVisibility(View.GONE); } - return Unit.INSTANCE; } private static class CameraStateProvider implements CameraScreenBrightnessController.CameraStateProvider { - private final CameraXController cameraController; + private final CameraController cameraController; - private CameraStateProvider(CameraXController cameraController) { + private CameraStateProvider(CameraController cameraController) { this.cameraController = cameraController; } @@ -695,20 +663,4 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { return cameraController.getImageCaptureFlashMode() == ImageCapture.FLASH_MODE_ON; } } - - private class CameraXOrientationListener extends OrientationEventListener { - - public CameraXOrientationListener(Context context) { - super(context); - } - - @Override - public void onOrientationChanged(int orientation) { - if (cameraController != null) { - if (RemoteConfig.customCameraXController()) { - cameraController.setImageRotation(orientation); - } - } - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXSelfieFlashHelper.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXSelfieFlashHelper.java index 28351a505a..2b96ff99e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXSelfieFlashHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXSelfieFlashHelper.java @@ -7,9 +7,7 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; - -import org.thoughtcrime.securesms.mediasend.camerax.CameraXController; -import org.thoughtcrime.securesms.mediasend.camerax.SignalCameraController; +import androidx.camera.view.CameraController; final class CameraXSelfieFlashHelper { @@ -17,7 +15,7 @@ final class CameraXSelfieFlashHelper { private static final float MAX_SELFIE_FLASH_ALPHA = 0.9f; private final Window window; - private final CameraXController camera; + private final CameraController camera; private final View selfieFlash; private float brightnessBeforeFlash; @@ -25,7 +23,7 @@ final class CameraXSelfieFlashHelper { private int flashMode = -1; CameraXSelfieFlashHelper(@NonNull Window window, - @NonNull CameraXController camera, + @NonNull CameraController camera, @NonNull View selfieFlash) { this.window = window; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java index ab314d6f2d..024e7f4236 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java @@ -17,6 +17,7 @@ import androidx.camera.core.ZoomState; import androidx.camera.video.FileDescriptorOutputOptions; import androidx.camera.video.Recording; import androidx.camera.video.VideoRecordEvent; +import androidx.camera.view.CameraController; import androidx.camera.view.PreviewView; import androidx.camera.view.video.AudioConfig; import androidx.core.content.ContextCompat; @@ -25,7 +26,6 @@ import androidx.fragment.app.Fragment; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.mediasend.camerax.CameraXController; import org.thoughtcrime.securesms.mediasend.camerax.CameraXModePolicy; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.ContextUtil; @@ -46,14 +46,14 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener private static final String VIDEO_DEBUG_LABEL = "video-capture"; private static final long VIDEO_SIZE = 10 * 1024 * 1024; - private final @NonNull Fragment fragment; - private final @NonNull PreviewView previewView; - private final @NonNull CameraXController cameraController; - private final @NonNull Callback callback; - private final @NonNull MemoryFileDescriptor memoryFileDescriptor; - private final @NonNull ValueAnimator updateProgressAnimator; - private final @NonNull Debouncer debouncer; - private final @NonNull CameraXModePolicy cameraXModePolicy; + private final @NonNull Fragment fragment; + 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 final @NonNull CameraXModePolicy cameraXModePolicy; private ValueAnimator cameraMetricsAnimator; @@ -87,7 +87,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener CameraXVideoCaptureHelper(@NonNull Fragment fragment, @NonNull CameraButtonView captureButton, - @NonNull CameraXController cameraController, + @NonNull CameraController cameraController, @NonNull PreviewView previewView, @NonNull MemoryFileDescriptor memoryFileDescriptor, @NonNull CameraXModePolicy cameraXModePolicy, diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXController.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXController.kt deleted file mode 100644 index a2fe406498..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXController.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.mediasend.camerax - -import android.Manifest -import android.content.Context -import android.util.Size -import androidx.annotation.MainThread -import androidx.annotation.RequiresApi -import androidx.annotation.RequiresPermission -import androidx.camera.core.CameraSelector -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageCapture -import androidx.camera.core.ZoomState -import androidx.camera.video.FileDescriptorOutputOptions -import androidx.camera.video.Recording -import androidx.camera.video.VideoRecordEvent -import androidx.camera.view.video.AudioConfig -import androidx.core.util.Consumer -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import com.google.common.util.concurrent.ListenableFuture -import java.util.concurrent.Executor - -interface CameraXController { - - fun isInitialized(): Boolean - - fun initializeAndBind(context: Context, lifecycleOwner: LifecycleOwner) - - @RequiresPermission(Manifest.permission.CAMERA) - fun bindToLifecycle(lifecycleOwner: LifecycleOwner, onCameraBoundListener: Runnable) - - @MainThread - fun unbind() - - @MainThread - fun takePicture(executor: Executor, callback: ImageCapture.OnImageCapturedCallback) - - @RequiresApi(26) - @MainThread - fun startRecording(outputOptions: FileDescriptorOutputOptions, audioConfig: AudioConfig, executor: Executor, videoSavedListener: Consumer): Recording - - @MainThread - fun setImageAnalysisAnalyzer(executor: Executor, analyzer: ImageAnalysis.Analyzer) - - @MainThread - fun setEnabledUseCases(useCaseFlags: Int) - - @MainThread - fun getImageCaptureFlashMode(): Int - - @MainThread - fun setPreviewTargetSize(size: Size) - - @MainThread - fun setImageCaptureTargetSize(size: Size) - - @MainThread - fun setImageRotation(rotation: Int) - - @MainThread - fun setImageCaptureFlashMode(flashMode: Int) - - @MainThread - fun setZoomRatio(ratio: Float): ListenableFuture - - @MainThread - fun getZoomState(): LiveData - - @MainThread - fun setCameraSelector(selector: CameraSelector) - - @MainThread - fun getCameraSelector(): CameraSelector - - @MainThread - fun hasCamera(selectedCamera: CameraSelector): Boolean - - @MainThread - fun addInitializationCompletedListener(executor: Executor, onComplete: () -> Unit) -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXModePolicy.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXModePolicy.kt index ff5d644db2..e31a2c5931 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXModePolicy.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXModePolicy.kt @@ -17,9 +17,9 @@ sealed class CameraXModePolicy { abstract val isQrScanEnabled: Boolean - abstract fun initialize(cameraController: CameraXController) + abstract fun initialize(cameraController: CameraController) - open fun initialize(cameraController: CameraXController, useCaseFlags: Int) { + open fun initialize(cameraController: CameraController, useCaseFlags: Int) { if (isQrScanEnabled) { cameraController.setEnabledUseCases(useCaseFlags or CameraController.IMAGE_ANALYSIS) } else { @@ -27,9 +27,9 @@ sealed class CameraXModePolicy { } } - open fun setToImage(cameraController: CameraXController) = Unit + open fun setToImage(cameraController: CameraController) = Unit - open fun setToVideo(cameraController: CameraXController) = Unit + open fun setToVideo(cameraController: CameraController) = Unit /** * The device supports having Image and Video enabled at the same time @@ -38,7 +38,7 @@ sealed class CameraXModePolicy { override val isVideoSupported: Boolean = true - override fun initialize(cameraController: CameraXController) { + override fun initialize(cameraController: CameraController) { super.initialize(cameraController, CameraController.IMAGE_CAPTURE or CameraController.VIDEO_CAPTURE) } } @@ -50,15 +50,15 @@ sealed class CameraXModePolicy { override val isVideoSupported: Boolean = true - override fun initialize(cameraController: CameraXController) { + override fun initialize(cameraController: CameraController) { setToImage(cameraController) } - override fun setToImage(cameraController: CameraXController) { + override fun setToImage(cameraController: CameraController) { super.initialize(cameraController, CameraController.IMAGE_CAPTURE) } - override fun setToVideo(cameraController: CameraXController) { + override fun setToVideo(cameraController: CameraController) { super.initialize(cameraController, CameraController.VIDEO_CAPTURE) } } @@ -70,7 +70,7 @@ sealed class CameraXModePolicy { override val isVideoSupported: Boolean = false - override fun initialize(cameraController: CameraXController) { + override fun initialize(cameraController: CameraController) { super.initialize(cameraController, CameraController.IMAGE_CAPTURE) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java index 24744f0776..69466e4afe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXUtil.java @@ -14,7 +14,6 @@ import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.os.Build; import android.util.Pair; -import android.util.Rational; import android.util.Size; import androidx.annotation.NonNull; @@ -127,21 +126,6 @@ public class CameraXUtil { : ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY; } - public static int getIdealResolution(int displayWidth, int displayHeight) { - int maxDisplay = Math.max(displayWidth, displayHeight); - return Math.max(maxDisplay, 1920); - } - - public static @NonNull Size buildResolutionForRatio(int longDimension, @NonNull Rational ratio, boolean isPortrait) { - int shortDimension = longDimension * ratio.getDenominator() / ratio.getNumerator(); - - if (isPortrait) { - return new Size(shortDimension, longDimension); - } else { - return new Size(longDimension, shortDimension); - } - } - private static byte[] transformByteArray(@NonNull byte[] data, @Nullable Rect cropRect, int rotation, boolean flip) throws IOException { Stopwatch stopwatch = new Stopwatch("transform"); Bitmap in; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/PlatformCameraController.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/PlatformCameraController.kt deleted file mode 100644 index 9223160b2e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/PlatformCameraController.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.mediasend.camerax - -import android.content.Context -import android.util.Size -import androidx.annotation.RequiresApi -import androidx.camera.core.CameraSelector -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageCapture -import androidx.camera.core.ZoomState -import androidx.camera.video.FallbackStrategy -import androidx.camera.video.FileDescriptorOutputOptions -import androidx.camera.video.Quality -import androidx.camera.video.QualitySelector -import androidx.camera.video.Recording -import androidx.camera.video.VideoRecordEvent -import androidx.camera.view.CameraController -import androidx.camera.view.LifecycleCameraController -import androidx.camera.view.video.AudioConfig -import androidx.core.util.Consumer -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import com.google.common.util.concurrent.ListenableFuture -import org.thoughtcrime.securesms.util.TextSecurePreferences -import java.util.concurrent.Executor - -class PlatformCameraController(context: Context) : CameraXController { - val delegate = LifecycleCameraController(context) - - override fun isInitialized(): Boolean { - return delegate.initializationFuture.isDone - } - - override fun initializeAndBind(context: Context, lifecycleOwner: LifecycleOwner) { - delegate.bindToLifecycle(lifecycleOwner) - delegate.setCameraSelector(CameraXUtil.toCameraSelector(TextSecurePreferences.getDirectCaptureCameraId(context))) - delegate.setTapToFocusEnabled(true) - delegate.setImageCaptureMode(CameraXUtil.getOptimalCaptureMode()) - delegate.setVideoCaptureQualitySelector(QualitySelector.from(Quality.HD, FallbackStrategy.lowerQualityThan(Quality.HD))) - } - - override fun bindToLifecycle(lifecycleOwner: LifecycleOwner, onCameraBoundListener: Runnable) { - delegate.bindToLifecycle(lifecycleOwner) - onCameraBoundListener.run() - } - - override fun unbind() { - delegate.unbind() - } - - override fun takePicture(executor: Executor, callback: ImageCapture.OnImageCapturedCallback) { - delegate.takePicture(executor, callback) - } - - @RequiresApi(26) - override fun startRecording(outputOptions: FileDescriptorOutputOptions, audioConfig: AudioConfig, executor: Executor, videoSavedListener: Consumer): Recording { - return delegate.startRecording(outputOptions, audioConfig, executor, videoSavedListener) - } - - override fun setImageAnalysisAnalyzer(executor: Executor, analyzer: ImageAnalysis.Analyzer) { - delegate.setImageAnalysisAnalyzer(executor, analyzer) - } - - override fun setEnabledUseCases(useCaseFlags: Int) { - delegate.setEnabledUseCases(useCaseFlags) - } - - override fun getImageCaptureFlashMode(): Int { - return delegate.imageCaptureFlashMode - } - - override fun setPreviewTargetSize(size: Size) { - delegate.previewTargetSize = CameraController.OutputSize(size) - } - - override fun setImageCaptureTargetSize(size: Size) { - delegate.imageCaptureTargetSize = CameraController.OutputSize(size) - } - - override fun setImageRotation(rotation: Int) { - throw NotImplementedError("Not supported by the platform camera controller!") - } - - override fun setImageCaptureFlashMode(flashMode: Int) { - delegate.imageCaptureFlashMode = flashMode - } - - override fun setZoomRatio(ratio: Float): ListenableFuture { - return delegate.setZoomRatio(ratio) - } - - override fun getZoomState(): LiveData { - return delegate.zoomState - } - - override fun setCameraSelector(selector: CameraSelector) { - delegate.cameraSelector = selector - } - - override fun getCameraSelector(): CameraSelector { - return delegate.cameraSelector - } - - override fun hasCamera(selectedCamera: CameraSelector): Boolean { - return delegate.hasCamera(selectedCamera) - } - - override fun addInitializationCompletedListener(executor: Executor, onComplete: () -> Unit) { - delegate.initializationFuture.addListener(onComplete, executor) - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/SignalCameraController.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/SignalCameraController.kt deleted file mode 100644 index 83dde1db0b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/SignalCameraController.kt +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.mediasend.camerax - -import android.Manifest -import android.content.Context -import android.util.Size -import android.view.MotionEvent -import android.view.ScaleGestureDetector -import android.view.Surface -import android.view.View -import android.view.ViewConfiguration -import androidx.annotation.MainThread -import androidx.annotation.RequiresApi -import androidx.annotation.RequiresPermission -import androidx.camera.core.Camera -import androidx.camera.core.CameraSelector -import androidx.camera.core.DisplayOrientedMeteringPointFactory -import androidx.camera.core.FocusMeteringAction -import androidx.camera.core.FocusMeteringResult -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageCapture -import androidx.camera.core.Preview -import androidx.camera.core.UseCase -import androidx.camera.core.UseCaseGroup -import androidx.camera.core.ZoomState -import androidx.camera.core.resolutionselector.AspectRatioStrategy -import androidx.camera.core.resolutionselector.ResolutionSelector -import androidx.camera.core.resolutionselector.ResolutionStrategy -import androidx.camera.extensions.ExtensionMode -import androidx.camera.extensions.ExtensionsManager -import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.camera.video.FallbackStrategy -import androidx.camera.video.FileDescriptorOutputOptions -import androidx.camera.video.Quality -import androidx.camera.video.QualitySelector -import androidx.camera.video.Recorder -import androidx.camera.video.Recording -import androidx.camera.video.VideoCapture -import androidx.camera.video.VideoRecordEvent -import androidx.camera.view.CameraController -import androidx.camera.view.PreviewView -import androidx.camera.view.video.AudioConfig -import androidx.core.content.ContextCompat -import androidx.core.util.Consumer -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import com.google.common.util.concurrent.FutureCallback -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture -import org.signal.core.util.ThreadUtil -import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.mediasend.camerax.SignalCameraController.InitializationListener -import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.thoughtcrime.securesms.util.ViewUtil -import org.thoughtcrime.securesms.util.visible -import java.util.concurrent.Executor -import kotlin.math.max -import kotlin.math.min - -/** - * This is a class to manage the camera resource, and relies on the AndroidX CameraX library. - * - * The API is a subset of the [CameraController] class, but with a few additions such as [setImageRotation]. - */ -class SignalCameraController( - private val context: Context, - private val lifecycleOwner: LifecycleOwner, - private val previewView: PreviewView, - private val focusIndicator: View -) : CameraXController { - companion object { - val TAG = Log.tag(SignalCameraController::class.java) - - private const val AF_SIZE = 1.0f / 6.0f - private const val AE_SIZE = AF_SIZE * 1.5f - - @JvmStatic - private fun isLandscape(surfaceRotation: Int): Boolean { - return surfaceRotation == Surface.ROTATION_90 || surfaceRotation == Surface.ROTATION_270 - } - } - - private val videoQualitySelector: QualitySelector = QualitySelector.from(Quality.HD, FallbackStrategy.lowerQualityThan(Quality.HD)) - private val imageMode = CameraXUtil.getOptimalCaptureMode() - - private val cameraProviderFuture: ListenableFuture = ProcessCameraProvider.getInstance(context) - private val scaleGestureDetector = ScaleGestureDetector(context, PinchToZoomGestureListener()) - private val initializationCompleteListeners: MutableSet = mutableSetOf() - private val customUseCases: MutableList = mutableListOf() - - private var tapToFocusEvents = 0 - private var listenerAdded = false - - private var imageRotation = 0 - private var recording: Recording? = null - private var previewTargetSize: Size? = null - private var imageCaptureTargetSize: Size? = null - private var cameraSelector: CameraSelector = CameraXUtil.toCameraSelector(TextSecurePreferences.getDirectCaptureCameraId(context)) - private var enabledUseCases: Int = CameraController.IMAGE_CAPTURE - - private var previewUseCase: Preview = createPreviewUseCase() - private var imageCaptureUseCase: ImageCapture = createImageCaptureUseCase() - private var videoCaptureUseCase: VideoCapture = createVideoCaptureRecorder() - - private lateinit var cameraProvider: ProcessCameraProvider - private lateinit var extensionsManager: ExtensionsManager - private lateinit var cameraProperty: Camera - - override fun isInitialized(): Boolean { - return this::cameraProvider.isInitialized && this::extensionsManager.isInitialized - } - - override fun initializeAndBind(context: Context, lifecycleOwner: LifecycleOwner) { - bindToLifecycle(lifecycleOwner) { Log.d(TAG, "Camera initialization and binding complete.") } - } - - @RequiresPermission(Manifest.permission.CAMERA) - override fun bindToLifecycle(lifecycleOwner: LifecycleOwner, onCameraBoundListener: Runnable) { - ThreadUtil.assertMainThread() - if (isInitialized()) { - bindToLifecycleInternal() - onCameraBoundListener.run() - } else if (!listenerAdded) { - cameraProviderFuture.addListener({ - cameraProvider = cameraProviderFuture.get() - val extensionsManagerFuture = - ExtensionsManager.getInstanceAsync(context, cameraProvider) - extensionsManagerFuture.addListener({ - extensionsManager = extensionsManagerFuture.get() - initializationCompleteListeners.forEach { it.onInitialized(cameraProvider) } - bindToLifecycleInternal() - onCameraBoundListener.run() - }, ContextCompat.getMainExecutor(context)) - }, ContextCompat.getMainExecutor(context)) - listenerAdded = true - } - } - - @MainThread - override fun unbind() { - ThreadUtil.assertMainThread() - cameraProvider.unbindAll() - } - - @MainThread - fun bindToLifecycleInternal() { - ThreadUtil.assertMainThread() - try { - if (!this::cameraProvider.isInitialized || !this::extensionsManager.isInitialized) { - Log.d(TAG, "Camera provider not yet initialized.") - return - } - - val extCameraSelector = if (extensionsManager.isExtensionAvailable(cameraSelector, ExtensionMode.AUTO)) { - Log.d(TAG, "Using CameraX ExtensionMode.AUTO") - extensionsManager.getExtensionEnabledCameraSelector( - cameraSelector, - ExtensionMode.AUTO - ) - } else { - Log.d(TAG, "Using standard camera selector") - cameraSelector - } - - val camera = cameraProvider.bindToLifecycle( - lifecycleOwner, - extCameraSelector, - buildUseCaseGroup() - ) - - initializeTapToFocusAndPinchToZoom(camera) - this.cameraProperty = camera - } catch (e: Exception) { - Log.e(TAG, "Use case binding failed", e) - } - } - - @MainThread - override fun setImageAnalysisAnalyzer(executor: Executor, analyzer: ImageAnalysis.Analyzer) { - ThreadUtil.assertMainThread() - val imageAnalysis = ImageAnalysis.Builder() - .setResolutionSelector(ResolutionSelector.Builder().setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY).build()) - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - - imageAnalysis.setAnalyzer(executor, analyzer) - - customUseCases += imageAnalysis - - if (isRecording()) { - stopRecording() - } - - tryToBindCamera() - } - - @MainThread - override fun takePicture(executor: Executor, callback: ImageCapture.OnImageCapturedCallback) { - ThreadUtil.assertMainThread() - assertImageEnabled() - imageCaptureUseCase.takePicture(executor, callback) - } - - @RequiresApi(26) - @MainThread - override fun startRecording(outputOptions: FileDescriptorOutputOptions, audioConfig: AudioConfig, executor: Executor, videoSavedListener: Consumer): Recording { - ThreadUtil.assertMainThread() - assertVideoEnabled() - - recording?.stop() - recording = null - val startedRecording = videoCaptureUseCase.output - .prepareRecording(context, outputOptions) - .apply { - if (audioConfig.audioEnabled) { - withAudioEnabled() - } - } - .start(ContextCompat.getMainExecutor(context)) { - videoSavedListener.accept(it) - if (it is VideoRecordEvent.Finalize) { - recording = null - } - } - - recording = startedRecording - return startedRecording - } - - @MainThread - override fun setEnabledUseCases(useCaseFlags: Int) { - ThreadUtil.assertMainThread() - if (enabledUseCases == useCaseFlags) { - return - } - - val oldEnabledUseCases = enabledUseCases - enabledUseCases = useCaseFlags - if (isRecording()) { - stopRecording() - } - tryToBindCamera { enabledUseCases = oldEnabledUseCases } - } - - @MainThread - override fun getImageCaptureFlashMode(): Int { - ThreadUtil.assertMainThread() - return imageCaptureUseCase.flashMode - } - - @MainThread - override fun setPreviewTargetSize(size: Size) { - ThreadUtil.assertMainThread() - if (size == previewTargetSize || previewTargetSize?.equals(size) == true) { - return - } - Log.d(TAG, "Setting Preview dimensions to $size") - previewTargetSize = size - if (this::cameraProvider.isInitialized) { - cameraProvider.unbind(previewUseCase) - } - previewUseCase = createPreviewUseCase() - - tryToBindCamera(null) - } - - @MainThread - override fun setImageCaptureTargetSize(size: Size) { - ThreadUtil.assertMainThread() - if (size == imageCaptureTargetSize || imageCaptureTargetSize?.equals(size) == true) { - return - } - imageCaptureTargetSize = size - if (this::cameraProvider.isInitialized) { - cameraProvider.unbind(imageCaptureUseCase) - } - imageCaptureUseCase = createImageCaptureUseCase() - tryToBindCamera(null) - } - - @MainThread - override fun setImageRotation(rotation: Int) { - ThreadUtil.assertMainThread() - val newRotation = UseCase.snapToSurfaceRotation(rotation.coerceIn(0, 359)) - - if (newRotation == imageRotation) { - return - } - - if (isLandscape(newRotation) != isLandscape(imageRotation)) { - imageCaptureTargetSize = imageCaptureTargetSize?.swap() - } - - videoCaptureUseCase.targetRotation = newRotation - imageCaptureUseCase.targetRotation = newRotation - - imageRotation = newRotation - } - - @MainThread - override fun setImageCaptureFlashMode(flashMode: Int) { - ThreadUtil.assertMainThread() - imageCaptureUseCase.flashMode = flashMode - } - - @MainThread - override fun setZoomRatio(ratio: Float): ListenableFuture { - ThreadUtil.assertMainThread() - return cameraProperty.cameraControl.setZoomRatio(ratio) - } - - @MainThread - override fun getZoomState(): LiveData { - ThreadUtil.assertMainThread() - return cameraProperty.cameraInfo.zoomState - } - - @MainThread - override fun setCameraSelector(selector: CameraSelector) { - ThreadUtil.assertMainThread() - if (selector == cameraSelector) { - return - } - - val oldCameraSelector: CameraSelector = cameraSelector - cameraSelector = selector - if (!this::cameraProvider.isInitialized) { - return - } - cameraProvider.unbindAll() - tryToBindCamera { cameraSelector = oldCameraSelector } - } - - @MainThread - override fun getCameraSelector(): CameraSelector { - ThreadUtil.assertMainThread() - return cameraSelector - } - - @MainThread - override fun hasCamera(selectedCamera: CameraSelector): Boolean { - ThreadUtil.assertMainThread() - return cameraProvider.hasCamera(selectedCamera) - } - - override fun addInitializationCompletedListener(executor: Executor, onComplete: () -> Unit) { - ThreadUtil.assertMainThread() - initializationCompleteListeners.add(InitializationListener { onComplete() }) - } - - @MainThread - private fun tryToBindCamera(restoreStateRunnable: (() -> Unit)? = null) { - ThreadUtil.assertMainThread() - try { - bindToLifecycleInternal() - } catch (e: RuntimeException) { - Log.i(TAG, "Could not re-bind camera!", e) - restoreStateRunnable?.invoke() - } - } - - @MainThread - private fun stopRecording() { - ThreadUtil.assertMainThread() - recording?.close() - } - - private fun createVideoCaptureRecorder() = VideoCapture.Builder( - Recorder.Builder() - .setQualitySelector(videoQualitySelector) - .build() - ) - .setTargetRotation(imageRotation) - .build() - - private fun createPreviewUseCase() = Preview.Builder() - .apply { - setTargetRotation(Surface.ROTATION_0) - val size = previewTargetSize - if (size != null) { - setResolutionSelector( - ResolutionSelector.Builder() - .setResolutionStrategy(ResolutionStrategy(size, ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER)) - .build() - ) - } - }.build() - .also { - it.setSurfaceProvider(previewView.surfaceProvider) - } - - private fun createImageCaptureUseCase(): ImageCapture = ImageCapture.Builder() - .apply { - setCaptureMode(imageMode) - setTargetRotation(imageRotation) - - val size = imageCaptureTargetSize - if (size != null) { - setResolutionSelector( - ResolutionSelector.Builder() - .setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY) - .setResolutionStrategy(ResolutionStrategy(size, ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER)) - .build() - ) - } - }.build() - - private fun buildUseCaseGroup() = UseCaseGroup.Builder().apply { - addUseCase(previewUseCase) - if (isUseCaseEnabled(CameraController.IMAGE_CAPTURE)) { - addUseCase(imageCaptureUseCase) - } else { - cameraProvider.unbind(imageCaptureUseCase) - } - if (isUseCaseEnabled(CameraController.VIDEO_CAPTURE)) { - addUseCase(videoCaptureUseCase) - } else { - cameraProvider.unbind(videoCaptureUseCase) - } - - for (useCase in customUseCases) { - addUseCase(useCase) - } - - previewView.getViewPort(Surface.ROTATION_0)?.let { setViewPort(it) } ?: Log.d(TAG, "ViewPort was null, not adding to UseCase builder.") - }.build() - - @MainThread - private fun initializeTapToFocusAndPinchToZoom(camera: Camera) { - ThreadUtil.assertMainThread() - previewView.setOnTouchListener { v: View?, event: MotionEvent -> - val isSingleTouch = event.pointerCount == 1 - val isUpEvent = event.action == MotionEvent.ACTION_UP - val notALongPress = (event.eventTime - event.downTime < ViewConfiguration.getLongPressTimeout()) - if (isSingleTouch && isUpEvent && notALongPress) { - focusAndMeterOnPoint(camera, event.x, event.y) - v?.performClick() - return@setOnTouchListener true - } - return@setOnTouchListener scaleGestureDetector.onTouchEvent(event) - } - } - - @MainThread - private fun focusAndMeterOnPoint(camera: Camera, x: Float, y: Float) { - ThreadUtil.assertMainThread() - val meteringPointFactory = DisplayOrientedMeteringPointFactory(previewView.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat()) - val afPoint = meteringPointFactory.createPoint(x, y, AF_SIZE) - val aePoint = meteringPointFactory.createPoint(x, y, AE_SIZE) - val action = FocusMeteringAction.Builder(afPoint, FocusMeteringAction.FLAG_AF) - .addPoint(aePoint, FocusMeteringAction.FLAG_AE) - .build() - - focusIndicator.x = x - (focusIndicator.width / 2) - focusIndicator.y = y - (focusIndicator.height / 2) - focusIndicator.visible = true - - tapToFocusEvents += 1 - - Futures.addCallback( - camera.cameraControl.startFocusAndMetering(action), - object : FutureCallback { - override fun onSuccess(result: FocusMeteringResult?) { - Log.d(TAG, "Tap to focus was successful? ${result?.isFocusSuccessful}") - tapToFocusEvents -= 1 - if (tapToFocusEvents <= 0) { - ViewUtil.fadeOut(focusIndicator, 80) - } - } - - override fun onFailure(t: Throwable) { - Log.d(TAG, "Tap to focus could not be completed due to an exception.", t) - tapToFocusEvents -= 1 - if (tapToFocusEvents <= 0) { - ViewUtil.fadeOut(focusIndicator, 80) - } - } - }, - ContextCompat.getMainExecutor(context) - ) - } - - @MainThread - private fun onPinchToZoom(pinchToZoomScale: Float) { - val zoomState = getZoomState().getValue() ?: return - var clampedRatio: Float = zoomState.zoomRatio * if (pinchToZoomScale > 1f) { - 1.0f + (pinchToZoomScale - 1.0f) * 2 - } else { - 1.0f - (1.0f - pinchToZoomScale) * 2 - } - clampedRatio = min( - max(clampedRatio.toDouble(), zoomState.minZoomRatio.toDouble()), - zoomState.maxZoomRatio.toDouble() - ).toFloat() - setZoomRatio(clampedRatio) - } - - private fun isRecording(): Boolean { - return recording != null - } - - private fun isUseCaseEnabled(mask: Int): Boolean { - return (enabledUseCases and mask) != 0 - } - - private fun assertVideoEnabled() { - if (!isUseCaseEnabled(CameraController.VIDEO_CAPTURE)) { - throw IllegalStateException("VideoCapture disabled.") - } - } - - private fun assertImageEnabled() { - if (!isUseCaseEnabled(CameraController.IMAGE_CAPTURE)) { - throw IllegalStateException("ImageCapture disabled.") - } - } - - private fun Size.swap(): Size { - return Size(this.height, this.width) - } - - inner class PinchToZoomGestureListener : ScaleGestureDetector.OnScaleGestureListener { - override fun onScale(detector: ScaleGestureDetector): Boolean { - onPinchToZoom(detector.scaleFactor) - return true - } - - override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true - - override fun onScaleEnd(detector: ScaleGestureDetector) = Unit - } - - fun interface InitializationListener { - fun onInitialized(cameraProvider: ProcessCameraProvider) - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index cc3557622f..c05bf5f755 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -45,7 +45,6 @@ import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.Debouncer import org.thoughtcrime.securesms.util.FullscreenHelper -import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.WindowUtil import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.visible @@ -92,7 +91,7 @@ class MediaSelectionActivity : override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { setContentView(R.layout.media_selection_activity) - if (RemoteConfig.customCameraXController) { + if (false) { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } @@ -163,6 +162,7 @@ class MediaSelectionActivity : TransitionManager.beginDelayedTransition(textStoryToggle, AutoTransition().setDuration(200)) cameraSelectedConstraintSet.applyTo(textStoryToggle) } + R.id.textStoryPostCreationFragment -> { textStoryToggle.visible = canDisplayStorySwitch() @@ -170,6 +170,7 @@ class MediaSelectionActivity : TransitionManager.beginDelayedTransition(textStoryToggle, AutoTransition().setDuration(200)) textSelectedConstraintSet.applyTo(textStoryToggle) } + else -> textStoryToggle.visible = false } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt index b0cefe4d80..96af0606d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/IncomingMessageObserver.kt @@ -8,7 +8,6 @@ import android.os.IBinder import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat import kotlinx.collections.immutable.toImmutableSet -import org.signal.core.util.ThreadUtil import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt index 6c2692a521..26c8ba75e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt @@ -1031,15 +1031,6 @@ object RemoteConfig { BuildConfig.MESSAGE_BACKUP_RESTORE_ENABLED || value.asBoolean(false) } - /** Whether or not to use the custom CameraX controller class */ - @JvmStatic - @get:JvmName("customCameraXController") - val customCameraXController: Boolean by remoteBoolean( - key = "android.cameraXCustomController", - defaultValue = false, - hotSwappable = true - ) - /** Whether unauthenticated chat web socket is backed by libsignal-net */ @JvmStatic @get:JvmName("libSignalWebSocketEnabled")