Add blocklist for mixed-mode capture.
This commit is contained in:
parent
afedbf40e3
commit
79b3b9190a
4 changed files with 115 additions and 19 deletions
|
@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.R;
|
|||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
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.v2.MediaAnimations;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton;
|
||||
|
@ -87,6 +88,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
|||
private MemoryFileDescriptor videoFileDescriptor;
|
||||
private LifecycleCameraController cameraController;
|
||||
private Disposable mostRecentItemDisposable = Disposable.disposed();
|
||||
private CameraXModePolicy cameraXModePolicy;
|
||||
|
||||
private boolean isThumbAvailable;
|
||||
private boolean isMediaSelected;
|
||||
|
@ -136,13 +138,18 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
|||
|
||||
this.previewView = view.findViewById(R.id.camerax_camera);
|
||||
this.controlsContainer = view.findViewById(R.id.camerax_controls_container);
|
||||
this.cameraXModePolicy = CameraXModePolicy.acquire(requireContext(),
|
||||
controller.getMediaConstraints(),
|
||||
requireArguments().getBoolean(IS_VIDEO_ENABLED, true));
|
||||
|
||||
Log.d(TAG, "Starting CameraX with mode policy " + cameraXModePolicy.getClass().getSimpleName());
|
||||
|
||||
cameraController = new LifecycleCameraController(requireContext());
|
||||
cameraController.bindToLifecycle(getViewLifecycleOwner());
|
||||
cameraController.setCameraSelector(CameraXUtil.toCameraSelector(TextSecurePreferences.getDirectCaptureCameraId(requireContext())));
|
||||
cameraController.setTapToFocusEnabled(true);
|
||||
cameraController.setImageCaptureMode(CameraXUtil.getOptimalCaptureMode());
|
||||
cameraController.setEnabledUseCases(getSupportedUseCases());
|
||||
cameraXModePolicy.initialize(cameraController);
|
||||
|
||||
previewView.setScaleType(PREVIEW_SCALE_TYPE);
|
||||
previewView.setController(cameraController);
|
||||
|
@ -335,7 +342,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
|||
galleryButton.setOnClickListener(v -> controller.onGalleryClicked());
|
||||
countButton.setOnClickListener(v -> controller.onCameraCountButtonClicked());
|
||||
|
||||
if (isVideoRecordingSupported(requireContext())) {
|
||||
if (Build.VERSION.SDK_INT >= 26 && cameraXModePolicy.isVideoSupported()) {
|
||||
try {
|
||||
closeVideoFileDescriptor();
|
||||
videoFileDescriptor = CameraXVideoCaptureHelper.createFileDescriptor(requireContext());
|
||||
|
@ -356,6 +363,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
|||
cameraController,
|
||||
previewView,
|
||||
videoFileDescriptor,
|
||||
cameraXModePolicy,
|
||||
maxDuration,
|
||||
new CameraXVideoCaptureHelper.Callback() {
|
||||
@Override
|
||||
|
@ -389,23 +397,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
|||
}
|
||||
}
|
||||
|
||||
@CameraController.UseCases
|
||||
private int getSupportedUseCases() {
|
||||
if (isVideoRecordingSupported(requireContext())) {
|
||||
return CameraController.IMAGE_CAPTURE | CameraController.VIDEO_CAPTURE;
|
||||
} else {
|
||||
return CameraController.IMAGE_CAPTURE;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isVideoRecordingSupported(@NonNull Context context) {
|
||||
return Build.VERSION.SDK_INT >= 26 &&
|
||||
requireArguments().getBoolean(IS_VIDEO_ENABLED, true) &&
|
||||
MediaConstraints.isVideoTranscodeAvailable() &&
|
||||
CameraXUtil.isMixedModeSupported(context) &&
|
||||
VideoUtil.getMaxVideoRecordDurationInSeconds(context, controller.getMediaConstraints()) > 0;
|
||||
}
|
||||
|
||||
private void displayVideoRecordingTooltipIfNecessary(CameraButtonView captureButton) {
|
||||
if (shouldDisplayVideoRecordingTooltip()) {
|
||||
int displayRotation = requireActivity().getWindowManager().getDefaultDisplay().getRotation();
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.bumptech.glide.util.Executors;
|
|||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXModePolicy;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
|
||||
|
@ -51,6 +52,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
|||
private final @NonNull MemoryFileDescriptor memoryFileDescriptor;
|
||||
private final @NonNull ValueAnimator updateProgressAnimator;
|
||||
private final @NonNull Debouncer debouncer;
|
||||
private final @NonNull CameraXModePolicy cameraXModePolicy;
|
||||
|
||||
private ValueAnimator cameraMetricsAnimator;
|
||||
|
||||
|
@ -81,6 +83,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
|||
@NonNull CameraController cameraController,
|
||||
@NonNull PreviewView previewView,
|
||||
@NonNull MemoryFileDescriptor memoryFileDescriptor,
|
||||
@NonNull CameraXModePolicy cameraXModePolicy,
|
||||
int maxVideoDurationSec,
|
||||
@NonNull Callback callback)
|
||||
{
|
||||
|
@ -91,6 +94,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
|||
this.callback = callback;
|
||||
this.updateProgressAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(TimeUnit.SECONDS.toMillis(maxVideoDurationSec));
|
||||
this.debouncer = new Debouncer(TimeUnit.SECONDS.toMillis(maxVideoDurationSec));
|
||||
this.cameraXModePolicy = cameraXModePolicy;
|
||||
|
||||
updateProgressAnimator.setInterpolator(new LinearInterpolator());
|
||||
updateProgressAnimator.addUpdateListener(anim -> captureButton.setProgress(anim.getAnimatedFraction()));
|
||||
|
@ -123,6 +127,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
|||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private void beginCameraRecording() {
|
||||
cameraXModePolicy.setToVideo(cameraController);
|
||||
this.cameraController.setZoomRatio(Objects.requireNonNull(this.cameraController.getZoomState().getValue()).getMinZoomRatio());
|
||||
callback.onVideoRecordStarted();
|
||||
shrinkCaptureArea();
|
||||
|
@ -196,6 +201,7 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
|||
|
||||
updateProgressAnimator.cancel();
|
||||
debouncer.clear();
|
||||
cameraXModePolicy.setToImage(cameraController);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package org.thoughtcrime.securesms.mediasend.camerax
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.camera.view.CameraController
|
||||
import androidx.camera.view.video.ExperimentalVideo
|
||||
import org.signal.core.util.asListContains
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.video.VideoUtil
|
||||
|
||||
/**
|
||||
* Describes device capabilities
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
@ExperimentalVideo
|
||||
sealed class CameraXModePolicy {
|
||||
|
||||
abstract val isVideoSupported: Boolean
|
||||
|
||||
abstract fun initialize(cameraController: CameraController)
|
||||
|
||||
open fun setToImage(cameraController: CameraController) = Unit
|
||||
|
||||
open fun setToVideo(cameraController: CameraController) = Unit
|
||||
|
||||
/**
|
||||
* The device supports having Image and Video enabled at the same time
|
||||
*/
|
||||
object Mixed : CameraXModePolicy() {
|
||||
|
||||
override val isVideoSupported: Boolean = true
|
||||
|
||||
override fun initialize(cameraController: CameraController) {
|
||||
cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE or CameraController.VIDEO_CAPTURE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The device supports image and video, but only one mode at a time.
|
||||
*/
|
||||
object Single : CameraXModePolicy() {
|
||||
|
||||
override val isVideoSupported: Boolean = true
|
||||
|
||||
override fun initialize(cameraController: CameraController) {
|
||||
setToImage(cameraController)
|
||||
}
|
||||
|
||||
override fun setToImage(cameraController: CameraController) {
|
||||
cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE)
|
||||
}
|
||||
|
||||
override fun setToVideo(cameraController: CameraController) {
|
||||
cameraController.setEnabledUseCases(CameraController.VIDEO_CAPTURE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The device supports taking images only.
|
||||
*/
|
||||
object ImageOnly : CameraXModePolicy() {
|
||||
|
||||
override val isVideoSupported: Boolean = false
|
||||
|
||||
override fun initialize(cameraController: CameraController) {
|
||||
cameraController.setEnabledUseCases(CameraController.IMAGE_CAPTURE)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun acquire(context: Context, mediaConstraints: MediaConstraints, isVideoEnabled: Boolean): CameraXModePolicy {
|
||||
val isVideoSupported = Build.VERSION.SDK_INT >= 26 &&
|
||||
isVideoEnabled &&
|
||||
MediaConstraints.isVideoTranscodeAvailable() &&
|
||||
VideoUtil.getMaxVideoRecordDurationInSeconds(context, mediaConstraints) > 0
|
||||
|
||||
val isMixedModeSupported = isVideoSupported &&
|
||||
Build.VERSION.SDK_INT >= 26 &&
|
||||
CameraXUtil.isMixedModeSupported(context) &&
|
||||
!FeatureFlags.cameraXMixedModelBlocklist().asListContains(Build.MODEL)
|
||||
|
||||
return when {
|
||||
isMixedModeSupported -> Mixed
|
||||
isVideoSupported -> Single
|
||||
else -> ImageOnly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -97,6 +97,7 @@ public final class FeatureFlags {
|
|||
private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList";
|
||||
private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList";
|
||||
private static final String CAMERAX_MODEL_BLOCKLIST = "android.cameraXModelBlockList";
|
||||
private static final String CAMERAX_MIXED_MODEL_BLOCKLIST = "android.cameraXMixedModelBlockList";
|
||||
private static final String RECIPIENT_MERGE_V2 = "android.recipientMergeV2";
|
||||
private static final String CDS_V2_LOAD_TEST = "android.cdsV2LoadTest";
|
||||
private static final String SMS_EXPORTER = "android.sms.exporter";
|
||||
|
@ -153,6 +154,7 @@ public final class FeatureFlags {
|
|||
TELECOM_MANUFACTURER_ALLOWLIST,
|
||||
TELECOM_MODEL_BLOCKLIST,
|
||||
CAMERAX_MODEL_BLOCKLIST,
|
||||
CAMERAX_MIXED_MODEL_BLOCKLIST,
|
||||
RECIPIENT_MERGE_V2,
|
||||
CDS_V2_LOAD_TEST,
|
||||
SMS_EXPORTER,
|
||||
|
@ -492,6 +494,11 @@ public final class FeatureFlags {
|
|||
return getString(CAMERAX_MODEL_BLOCKLIST, "");
|
||||
}
|
||||
|
||||
/** A comma-separated list of manufacturers that should *not* use CameraX mixed mode. */
|
||||
public static @NonNull String cameraXMixedModelBlocklist() {
|
||||
return getString(CAMERAX_MIXED_MODEL_BLOCKLIST, "");
|
||||
}
|
||||
|
||||
/** Whether or not hardware AEC should be used for calling on devices older than API 29. */
|
||||
public static boolean useHardwareAecIfOlderThanApi29() {
|
||||
return getBoolean(USE_HARDWARE_AEC_IF_OLD, false);
|
||||
|
|
Loading…
Add table
Reference in a new issue