kopia lustrzana https://github.com/ryukoposting/Signal-Android
559 wiersze
22 KiB
Java
559 wiersze
22 KiB
Java
package org.thoughtcrime.securesms.mediasend;
|
|
|
|
import android.animation.Animator;
|
|
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;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.animation.Animation;
|
|
import android.view.animation.AnimationUtils;
|
|
import android.view.animation.DecelerateInterpolator;
|
|
import android.view.animation.RotateAnimation;
|
|
import android.widget.ImageView;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.RequiresApi;
|
|
import androidx.camera.core.CameraSelector;
|
|
import androidx.camera.core.ImageCapture;
|
|
import androidx.camera.core.ImageCaptureException;
|
|
import androidx.camera.core.ImageProxy;
|
|
import androidx.camera.view.CameraController;
|
|
import androidx.camera.view.LifecycleCameraController;
|
|
import androidx.camera.view.PreviewView;
|
|
import androidx.camera.view.video.ExperimentalVideo;
|
|
import androidx.cardview.widget.CardView;
|
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
import androidx.constraintlayout.widget.ConstraintSet;
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
import com.bumptech.glide.Glide;
|
|
import com.bumptech.glide.util.Executors;
|
|
|
|
import org.signal.core.util.Stopwatch;
|
|
import org.signal.core.util.concurrent.SimpleTask;
|
|
import org.signal.core.util.logging.Log;
|
|
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.CameraXFlashToggleView;
|
|
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
|
|
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.util.MemoryFileDescriptor;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
import org.thoughtcrime.securesms.video.VideoUtil;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
|
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
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 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;
|
|
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;
|
|
|
|
public static CameraXFragment newInstanceForAvatarCapture() {
|
|
CameraXFragment fragment = new CameraXFragment();
|
|
Bundle args = new Bundle();
|
|
|
|
args.putBoolean(IS_VIDEO_ENABLED, false);
|
|
fragment.setArguments(args);
|
|
|
|
return fragment;
|
|
}
|
|
|
|
public static CameraXFragment newInstance() {
|
|
CameraXFragment fragment = new CameraXFragment();
|
|
|
|
fragment.setArguments(new Bundle());
|
|
|
|
return fragment;
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(@NonNull Context context) {
|
|
super.onAttach(context);
|
|
|
|
if (getActivity() instanceof Controller) {
|
|
this.controller = (Controller) getActivity();
|
|
} else if (getParentFragment() instanceof Controller) {
|
|
this.controller = (Controller) getParentFragment();
|
|
}
|
|
|
|
if (controller == null) {
|
|
throw new IllegalStateException("Parent must implement controller interface.");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
return inflater.inflate(R.layout.camerax_fragment, container, false);
|
|
}
|
|
|
|
@SuppressLint("MissingPermission")
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
ViewGroup cameraParent = view.findViewById(R.id.camerax_camera_parent);
|
|
|
|
this.previewView = view.findViewById(R.id.camerax_camera);
|
|
this.controlsContainer = view.findViewById(R.id.camerax_controls_container);
|
|
|
|
cameraController = new LifecycleCameraController(requireContext());
|
|
cameraController.bindToLifecycle(getViewLifecycleOwner());
|
|
cameraController.setCameraSelector(CameraXUtil.toCameraSelector(TextSecurePreferences.getDirectCaptureCameraId(requireContext())));
|
|
cameraController.setTapToFocusEnabled(true);
|
|
cameraController.setImageCaptureMode(CameraXUtil.getOptimalCaptureMode());
|
|
cameraController.setEnabledUseCases(getSupportedUseCases());
|
|
|
|
previewView.setScaleType(PREVIEW_SCALE_TYPE);
|
|
previewView.setController(cameraController);
|
|
|
|
onOrientationChanged();
|
|
|
|
view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
// Let's assume portrait for now, so 9:16
|
|
float aspectRatio = CameraFragment.getAspectRatioForOrientation(Configuration.ORIENTATION_PORTRAIT);
|
|
float width = right - left;
|
|
float height = Math.min((1f / aspectRatio) * width, bottom - top);
|
|
|
|
ViewGroup.LayoutParams params = cameraParent.getLayoutParams();
|
|
|
|
// If there's a mismatch...
|
|
if (params.height != (int) height) {
|
|
params.width = (int) width;
|
|
params.height = (int) height;
|
|
|
|
cameraParent.setLayoutParams(params);
|
|
cameraController.setPreviewTargetSize(new CameraController.OutputSize(new Size((int) width, (int) height)));
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
|
|
cameraController.bindToLifecycle(getViewLifecycleOwner());
|
|
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroyView() {
|
|
super.onDestroyView();
|
|
mostRecentItemDisposable.dispose();
|
|
closeVideoFileDescriptor();
|
|
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
}
|
|
|
|
@Override
|
|
public void fadeOutControls(@NonNull Runnable onEndAction) {
|
|
controlsContainer.setEnabled(false);
|
|
controlsContainer.animate()
|
|
.setDuration(250)
|
|
.alpha(0f)
|
|
.setInterpolator(MediaAnimations.getInterpolator())
|
|
.setListener(new AnimationCompleteListener() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
controlsContainer.setEnabled(true);
|
|
onEndAction.run();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void fadeInControls() {
|
|
controlsContainer.setEnabled(false);
|
|
controlsContainer.animate()
|
|
.setDuration(250)
|
|
.alpha(1f)
|
|
.setInterpolator(MediaAnimations.getInterpolator())
|
|
.setListener(new AnimationCompleteListener() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
controlsContainer.setEnabled(true);
|
|
}
|
|
});
|
|
}
|
|
|
|
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.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();
|
|
}
|
|
|
|
private void presentRecentItemThumbnail(@Nullable Media media) {
|
|
ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button);
|
|
|
|
if (media != null) {
|
|
thumbnail.setVisibility(View.VISIBLE);
|
|
Glide.with(this)
|
|
.load(new DecryptableUri(media.getUri()))
|
|
.centerCrop()
|
|
.into(thumbnail);
|
|
} else {
|
|
thumbnail.setVisibility(View.GONE);
|
|
thumbnail.setImageResource(0);
|
|
}
|
|
|
|
isThumbAvailable = media != null;
|
|
updateGalleryVisibility();
|
|
}
|
|
|
|
@Override
|
|
public void presentHud(int selectedMediaCount) {
|
|
MediaCountIndicatorButton countButton = controlsContainer.findViewById(R.id.camera_review_button);
|
|
|
|
if (selectedMediaCount > 0) {
|
|
countButton.setVisibility(View.VISIBLE);
|
|
countButton.setCount(selectedMediaCount);
|
|
} else {
|
|
countButton.setVisibility(View.GONE);
|
|
}
|
|
|
|
isMediaSelected = selectedMediaCount > 0;
|
|
updateGalleryVisibility();
|
|
}
|
|
|
|
private void updateGalleryVisibility() {
|
|
View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background);
|
|
|
|
if (isMediaSelected || !isThumbAvailable) {
|
|
cameraGalleryContainer.setVisibility(View.GONE);
|
|
} else {
|
|
cameraGalleryContainer.setVisibility(View.VISIBLE);
|
|
}
|
|
}
|
|
|
|
private void initializeViewFinderAndControlsPositioning() {
|
|
CardView cameraCard = requireView().findViewById(R.id.camerax_camera_parent);
|
|
View controls = requireView().findViewById(R.id.camerax_controls_container);
|
|
CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity());
|
|
|
|
if (!cameraDisplay.getRoundViewFinderCorners()) {
|
|
cameraCard.setRadius(0f);
|
|
}
|
|
|
|
ViewUtil.setBottomMargin(controls, cameraDisplay.getCameraCaptureMarginBottom(getResources()));
|
|
|
|
if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) {
|
|
ConstraintSet constraintSet = new ConstraintSet();
|
|
constraintSet.clone((ConstraintLayout) requireView());
|
|
constraintSet.connect(R.id.camerax_camera_parent, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP);
|
|
constraintSet.applyTo((ConstraintLayout) requireView());
|
|
} else {
|
|
ViewUtil.setBottomMargin(cameraCard, cameraDisplay.getCameraViewportMarginBottom());
|
|
}
|
|
}
|
|
|
|
@SuppressLint({ "ClickableViewAccessibility", "MissingPermission" })
|
|
private void initControls() {
|
|
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);
|
|
View countButton = requireView().findViewById(R.id.camera_review_button);
|
|
CameraXFlashToggleView flashButton = requireView().findViewById(R.id.camera_flash_button);
|
|
|
|
initializeViewFinderAndControlsPositioning();
|
|
|
|
mostRecentItemDisposable.dispose();
|
|
mostRecentItemDisposable = controller.getMostRecentMediaItem()
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.subscribe(item -> presentRecentItemThumbnail(item.orElse(null)));
|
|
|
|
selfieFlash = requireView().findViewById(R.id.camera_selfie_flash);
|
|
|
|
captureButton.setOnClickListener(v -> {
|
|
captureButton.setEnabled(false);
|
|
flipButton.setEnabled(false);
|
|
flashButton.setEnabled(false);
|
|
onCaptureClicked();
|
|
});
|
|
|
|
previewView.setScaleType(PREVIEW_SCALE_TYPE);
|
|
|
|
cameraController.getInitializationFuture()
|
|
.addListener(() -> initializeFlipButton(flipButton, flashButton), Executors.mainThreadExecutor());
|
|
|
|
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());
|
|
|
|
if (isVideoRecordingSupported(requireContext())) {
|
|
try {
|
|
closeVideoFileDescriptor();
|
|
videoFileDescriptor = CameraXVideoCaptureHelper.createFileDescriptor(requireContext());
|
|
|
|
Animation inAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in);
|
|
Animation outAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out);
|
|
|
|
int maxDuration = VideoUtil.getMaxVideoRecordDurationInSeconds(requireContext(), controller.getMediaConstraints());
|
|
if (controller.getMaxVideoDuration() > 0) {
|
|
maxDuration = controller.getMaxVideoDuration();
|
|
}
|
|
|
|
Log.d(TAG, "Max duration: " + maxDuration + " sec");
|
|
|
|
captureButton.setVideoCaptureListener(new CameraXVideoCaptureHelper(
|
|
this,
|
|
captureButton,
|
|
cameraController,
|
|
previewView,
|
|
videoFileDescriptor,
|
|
maxDuration,
|
|
new CameraXVideoCaptureHelper.Callback() {
|
|
@Override
|
|
public void onVideoRecordStarted() {
|
|
hideAndDisableControlsForVideoRecording(captureButton, flashButton, flipButton, outAnimation);
|
|
}
|
|
|
|
@Override
|
|
public void onVideoSaved(@NonNull FileDescriptor fd) {
|
|
showAndEnableControlsAfterVideoRecording(captureButton, flashButton, flipButton, inAnimation);
|
|
controller.onVideoCaptured(fd);
|
|
}
|
|
|
|
@Override
|
|
public void onVideoError(@Nullable Throwable cause) {
|
|
showAndEnableControlsAfterVideoRecording(captureButton, flashButton, flipButton, inAnimation);
|
|
controller.onVideoCaptureError();
|
|
}
|
|
}
|
|
));
|
|
displayVideoRecordingTooltipIfNecessary(captureButton);
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Video capture is not supported on this device.", e);
|
|
}
|
|
} else {
|
|
Log.i(TAG, "Video capture not supported. " +
|
|
"API: " + Build.VERSION.SDK_INT + ", " +
|
|
"MFD: " + MemoryFileDescriptor.supported() + ", " +
|
|
"Camera: " + CameraXUtil.getLowestSupportedHardwareLevel(requireContext()) + ", " +
|
|
"MaxDuration: " + VideoUtil.getMaxVideoRecordDurationInSeconds(requireContext(), controller.getMediaConstraints()) + " sec");
|
|
}
|
|
}
|
|
|
|
@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();
|
|
|
|
TooltipPopup.forTarget(captureButton)
|
|
.setOnDismissListener(this::neverDisplayVideoRecordingTooltipAgain)
|
|
.setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.core_ultramarine))
|
|
.setTextColor(ContextCompat.getColor(requireContext(), R.color.signal_text_toolbar_title))
|
|
.setText(R.string.CameraXFragment_tap_for_photo_hold_for_video)
|
|
.show(displayRotation == Surface.ROTATION_0 || displayRotation == Surface.ROTATION_180 ? TooltipPopup.POSITION_ABOVE : TooltipPopup.POSITION_START);
|
|
}
|
|
}
|
|
|
|
private boolean shouldDisplayVideoRecordingTooltip() {
|
|
return !TextSecurePreferences.hasSeenVideoRecordingTooltip(requireContext()) && MediaConstraints.isVideoTranscodeAvailable();
|
|
}
|
|
|
|
private void neverDisplayVideoRecordingTooltipAgain() {
|
|
Context context = getContext();
|
|
if (context != null) {
|
|
TextSecurePreferences.setHasSeenVideoRecordingTooltip(requireContext(), true);
|
|
}
|
|
}
|
|
|
|
private void hideAndDisableControlsForVideoRecording(@NonNull View captureButton,
|
|
@NonNull View flashButton,
|
|
@NonNull View flipButton,
|
|
@NonNull Animation outAnimation)
|
|
{
|
|
captureButton.setEnabled(false);
|
|
flashButton.startAnimation(outAnimation);
|
|
flashButton.setVisibility(View.INVISIBLE);
|
|
flipButton.startAnimation(outAnimation);
|
|
flipButton.setVisibility(View.INVISIBLE);
|
|
}
|
|
|
|
private void showAndEnableControlsAfterVideoRecording(@NonNull View captureButton,
|
|
@NonNull View flashButton,
|
|
@NonNull View flipButton,
|
|
@NonNull Animation inAnimation)
|
|
{
|
|
requireActivity().runOnUiThread(() -> {
|
|
captureButton.setEnabled(true);
|
|
flashButton.startAnimation(inAnimation);
|
|
flashButton.setVisibility(View.VISIBLE);
|
|
flipButton.startAnimation(inAnimation);
|
|
flipButton.setVisibility(View.VISIBLE);
|
|
});
|
|
}
|
|
|
|
private void onCaptureClicked() {
|
|
Stopwatch stopwatch = new Stopwatch("Capture");
|
|
|
|
CameraXSelfieFlashHelper flashHelper = new CameraXSelfieFlashHelper(
|
|
requireActivity().getWindow(),
|
|
cameraController,
|
|
selfieFlash
|
|
);
|
|
|
|
flashHelper.onWillTakePicture();
|
|
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, flip);
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Failed to encode captured image.", e);
|
|
return null;
|
|
} finally {
|
|
image.close();
|
|
}
|
|
}, result -> {
|
|
stopwatch.split("transformed");
|
|
stopwatch.stop(TAG);
|
|
|
|
if (result != null) {
|
|
controller.onImageCaptured(result.getData(), result.getWidth(), result.getHeight());
|
|
} else {
|
|
controller.onCameraError();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onError(ImageCaptureException exception) {
|
|
Log.w(TAG, "Failed to capture image", exception);
|
|
flashHelper.endFlash();
|
|
controller.onCameraError();
|
|
}
|
|
});
|
|
|
|
flashHelper.startFlash();
|
|
}
|
|
|
|
private void closeVideoFileDescriptor() {
|
|
if (videoFileDescriptor != null) {
|
|
try {
|
|
videoFileDescriptor.close();
|
|
videoFileDescriptor = null;
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Failed to close video file descriptor", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint({ "MissingPermission" })
|
|
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;
|
|
}
|
|
|
|
if (cameraController.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) && cameraController.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)) {
|
|
flipButton.setVisibility(View.VISIBLE);
|
|
flipButton.setOnClickListener(v -> {
|
|
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(cameraController.getImageCaptureFlashMode() >= ImageCapture.FLASH_MODE_AUTO);
|
|
flashButton.setFlash(cameraController.getImageCaptureFlashMode());
|
|
});
|
|
|
|
GestureDetector gestureDetector = new GestureDetector(requireContext(), new GestureDetector.SimpleOnGestureListener() {
|
|
@Override
|
|
public boolean onDoubleTap(MotionEvent e) {
|
|
if (flipButton.isEnabled()) {
|
|
flipButton.performClick();
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
|
|
previewView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
|
|
|
|
} else {
|
|
flipButton.setVisibility(View.GONE);
|
|
}
|
|
}
|
|
}
|