diff --git a/app/src/main/java/androidx/camera/view/SignalCameraView.java b/app/src/main/java/androidx/camera/view/SignalCameraView.java
deleted file mode 100644
index 5d4ff28025..0000000000
--- a/app/src/main/java/androidx/camera/view/SignalCameraView.java
+++ /dev/null
@@ -1,866 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.view;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RequiresPermission;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.RestrictTo.Scope;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.FocusMeteringResult;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Logger;
-import androidx.camera.core.MeteringPoint;
-import androidx.camera.core.MeteringPointFactory;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.core.util.Consumer;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LiveData;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.mediasend.RotationListener;
-
-import java.io.File;
-import java.util.concurrent.Executor;
-
-import io.reactivex.rxjava3.disposables.Disposable;
-
-/**
- * A {@link View} that displays a preview of the camera with methods {@link
- * #takePicture(Executor, OnImageCapturedCallback)},
- * {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
- * {@link #startRecording(File , Executor , OnVideoSavedCallback callback)}
- * and {@link #stopRecording()}.
- *
- *
Because the Camera is a limited resource and consumes a high amount of power, CameraView must
- * be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
- * LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
- */
-@RequiresApi(21)
-@SuppressLint("RestrictedApi")
-public final class SignalCameraView extends FrameLayout {
- static final String TAG = Log.tag(SignalCameraView.class);
-
- static final int INDEFINITE_VIDEO_DURATION = -1;
- static final int INDEFINITE_VIDEO_SIZE = -1;
-
- private static final String EXTRA_SUPER = "super";
- private static final String EXTRA_ZOOM_RATIO = "zoom_ratio";
- private static final String EXTRA_PINCH_TO_ZOOM_ENABLED = "pinch_to_zoom_enabled";
- private static final String EXTRA_FLASH = "flash";
- private static final String EXTRA_MAX_VIDEO_DURATION = "max_video_duration";
- private static final String EXTRA_MAX_VIDEO_SIZE = "max_video_size";
- private static final String EXTRA_SCALE_TYPE = "scale_type";
- private static final String EXTRA_CAMERA_DIRECTION = "camera_direction";
- private static final String EXTRA_CAPTURE_MODE = "captureMode";
-
- private static final int LENS_FACING_NONE = 0;
- private static final int LENS_FACING_FRONT = 1;
- private static final int LENS_FACING_BACK = 2;
- private static final int FLASH_MODE_AUTO = 1;
- private static final int FLASH_MODE_ON = 2;
- private static final int FLASH_MODE_OFF = 4;
- // For tap-to-focus
- private long mDownEventTimestamp;
- // For pinch-to-zoom
- private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
- private boolean mIsPinchToZoomEnabled = true;
- SignalCameraXModule mCameraModule;
- private final DisplayManager.DisplayListener mDisplayListener =
- new DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- mCameraModule.invalidateView();
- }
- };
- private PreviewView mPreviewView;
- // For accessibility event
- private MotionEvent mUpEvent;
-
- // BEGIN Custom Signal Code Block
- private Consumer errorConsumer;
- private Throwable pendingError;
-
- private RotationListener rotationListener;
- private Disposable rotationDisposable;
- private RotationListener.Rotation rotation;
- // END Custom Signal Code Block
-
- public SignalCameraView(@NonNull Context context) {
- this(context, null);
- }
-
- public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- @RequiresApi(21)
- public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init(context, attrs);
- }
-
- /**
- * Binds control of the camera used by this view to the given lifecycle.
- *
- * This links opening/closing the camera to the given lifecycle. The camera will not operate
- * unless this method is called with a valid {@link LifecycleOwner} that is not in the {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state. Call this method only once camera
- * permissions have been obtained.
- *
- *
Once the provided lifecycle has transitioned to a {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state, CameraView must be bound to a new
- * lifecycle through this method in order to operate the camera.
- *
- * @param lifecycleOwner The lifecycle that will control this view's camera
- * @throws IllegalArgumentException if provided lifecycle is in a {@link
- * androidx.lifecycle.Lifecycle.State#DESTROYED} state.
- * @throws IllegalStateException if camera permissions are not granted.
- */
- // BEGIN Custom Signal Code Block
-
- @RequiresPermission(permission.CAMERA)
- public void bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner, Consumer errorConsumer) {
- mCameraModule.bindToLifecycle(lifecycleOwner);
- this.errorConsumer = errorConsumer;
- if (pendingError != null) {
- errorConsumer.accept(pendingError);
- }
- }
- // END Custom Signal Code Block
-
-
- private void init(Context context, @Nullable AttributeSet attrs) {
- addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
-
- // Begin custom signal code block
- rotationListener = new RotationListener(context);
- mPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
- mCameraModule = new SignalCameraXModule(this, error -> {
- if (errorConsumer != null) {
- errorConsumer.accept(error);
- } else {
- pendingError = error;
- }
- });
- // End custom signal code block
-
- if (attrs != null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
- setScaleType(
- PreviewView.ScaleType.fromId(
- a.getInteger(R.styleable.CameraView_scaleType,
- getScaleType().getId())));
- setPinchToZoomEnabled(
- a.getBoolean(
- R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
- setCaptureMode(
- CaptureMode.fromId(
- a.getInteger(R.styleable.CameraView_captureMode,
- getCaptureMode().getId())));
-
- int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
- switch (lensFacing) {
- case LENS_FACING_NONE:
- setCameraLensFacing(null);
- break;
- case LENS_FACING_FRONT:
- setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
- break;
- case LENS_FACING_BACK:
- setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
- break;
- default:
- // Unhandled event.
- }
-
- int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
- switch (flashMode) {
- case FLASH_MODE_AUTO:
- setFlash(ImageCapture.FLASH_MODE_AUTO);
- break;
- case FLASH_MODE_ON:
- setFlash(ImageCapture.FLASH_MODE_ON);
- break;
- case FLASH_MODE_OFF:
- setFlash(ImageCapture.FLASH_MODE_OFF);
- break;
- default:
- // Unhandled event.
- }
-
- a.recycle();
- }
-
- if (getBackground() == null) {
- setBackgroundColor(0xFF111111);
- }
-
- mPinchToZoomGestureDetector = new PinchToZoomGestureDetector(context);
- }
-
- @Override
- @NonNull
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- }
-
- @Override
- @NonNull
- protected Parcelable onSaveInstanceState() {
- // TODO(b/113884082): Decide what belongs here or what should be invalidated on
- // configuration
- // change
- Bundle state = new Bundle();
- state.putParcelable(EXTRA_SUPER, super.onSaveInstanceState());
- state.putInt(EXTRA_SCALE_TYPE, getScaleType().getId());
- state.putFloat(EXTRA_ZOOM_RATIO, getZoomRatio());
- state.putBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED, isPinchToZoomEnabled());
- state.putString(EXTRA_FLASH, FlashModeConverter.nameOf(getFlash()));
- state.putLong(EXTRA_MAX_VIDEO_DURATION, getMaxVideoDuration());
- state.putLong(EXTRA_MAX_VIDEO_SIZE, getMaxVideoSize());
- if (getCameraLensFacing() != null) {
- state.putString(EXTRA_CAMERA_DIRECTION,
- LensFacingConverter.nameOf(getCameraLensFacing()));
- }
- state.putInt(EXTRA_CAPTURE_MODE, getCaptureMode().getId());
- return state;
- }
-
- @Override
- protected void onRestoreInstanceState(@Nullable Parcelable savedState) {
- // TODO(b/113884082): Decide what belongs here or what should be invalidated on
- // configuration
- // change
- if (savedState instanceof Bundle) {
- Bundle state = (Bundle) savedState;
- super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
- setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
- setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
- setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
- setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
- setMaxVideoDuration(state.getLong(EXTRA_MAX_VIDEO_DURATION));
- setMaxVideoSize(state.getLong(EXTRA_MAX_VIDEO_SIZE));
- String lensFacingString = state.getString(EXTRA_CAMERA_DIRECTION);
- setCameraLensFacing(
- TextUtils.isEmpty(lensFacingString)
- ? null
- : LensFacingConverter.valueOf(lensFacingString));
- setCaptureMode(CaptureMode.fromId(state.getInt(EXTRA_CAPTURE_MODE)));
- } else {
- super.onRestoreInstanceState(savedState);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- DisplayManager dpyMgr =
- (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
- rotationDisposable = rotationListener.getObservable().distinctUntilChanged().subscribe(rotation -> {
- this.rotation = rotation;
- });
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- DisplayManager dpyMgr =
- (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- dpyMgr.unregisterDisplayListener(mDisplayListener);
- rotationDisposable.dispose();
- }
-
- /**
- * Gets the {@link LiveData} of the underlying {@link PreviewView}'s
- * {@link PreviewView.StreamState}.
- *
- * @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
- * get current value by {@link LiveData#getValue()} or register a observer by
- * {@link LiveData#observe}.
- * @see PreviewView#getPreviewStreamState()
- */
- @NonNull
- public LiveData getPreviewStreamState() {
- return mPreviewView.getPreviewStreamState();
- }
-
- @NonNull
- PreviewView getPreviewView() {
- return mPreviewView;
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Since bindToLifecycle will depend on the measured dimension, only call it when measured
- // dimension is not 0x0
- if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- mCameraModule.bindToLifecycleAfterViewMeasured();
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // In case that the CameraView size is always set as 0x0, we still need to trigger to force
- // binding to lifecycle
- mCameraModule.bindToLifecycleAfterViewMeasured();
-
- mCameraModule.invalidateView();
- super.onLayout(changed, left, top, right, bottom);
- }
-
- // BEGIN Custom Signal Code Block
- /**
- * @return One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link
- * Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
- */
- int getDisplaySurfaceRotation() {
- if (rotation == null) {
- Display display = getDisplay();
-
- // Null when the View is detached. If we were in the middle of a background operation,
- // better to not NPE. When the background operation finishes, it'll realize that the camera
- // was closed.
- if (display == null) {
- return 0;
- }
-
- return display.getRotation();
- } else {
- return rotation.getSurfaceRotation();
- }
- }
- // END Custom Signal Code Block
-
- /**
- * Returns the scale type used to scale the preview.
- *
- * @return The current {@link PreviewView.ScaleType}.
- */
- @NonNull
- public PreviewView.ScaleType getScaleType() {
- return mPreviewView.getScaleType();
- }
-
- /**
- * Sets the view finder scale type.
- *
- * This controls how the view finder should be scaled and positioned within the view.
- *
- * @param scaleType The desired {@link PreviewView.ScaleType}.
- */
- public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
- mPreviewView.setScaleType(scaleType);
- }
-
- /**
- * Returns the scale type used to scale the preview.
- *
- * @return The current {@link CaptureMode}.
- */
- @NonNull
- public CaptureMode getCaptureMode() {
- return mCameraModule.getCaptureMode();
- }
-
- /**
- * Sets the CameraView capture mode
- *
- *
This controls only image or video capture function is enabled or both are enabled.
- *
- * @param captureMode The desired {@link CaptureMode}.
- */
- public void setCaptureMode(@NonNull CaptureMode captureMode) {
- mCameraModule.setCaptureMode(captureMode);
- }
-
- /**
- * Returns the maximum duration of videos, or {@link #INDEFINITE_VIDEO_DURATION} if there is no
- * timeout.
- *
- * @hide Not currently implemented.
- */
- @RestrictTo(Scope.LIBRARY_GROUP)
- public long getMaxVideoDuration() {
- return mCameraModule.getMaxVideoDuration();
- }
-
- /**
- * Sets the maximum video duration before
- * {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)} is called
- * automatically.
- * Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
- */
- private void setMaxVideoDuration(long duration) {
- mCameraModule.setMaxVideoDuration(duration);
- }
-
- /**
- * Returns the maximum size of videos in bytes, or {@link #INDEFINITE_VIDEO_SIZE} if there is no
- * timeout.
- */
- private long getMaxVideoSize() {
- return mCameraModule.getMaxVideoSize();
- }
-
- /**
- * Sets the maximum video size in bytes before
- * {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)}
- * is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
- */
- private void setMaxVideoSize(long size) {
- mCameraModule.setMaxVideoSize(size);
- }
-
- /**
- * Takes a picture, and calls {@link OnImageCapturedCallback#onCaptureSuccess(ImageProxy)}
- * once when done.
- *
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure callbacks.
- */
- public void takePicture(@NonNull Executor executor, @NonNull OnImageCapturedCallback callback) {
- mCameraModule.takePicture(executor, callback);
- }
-
- /**
- * Takes a picture and calls
- * {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
- *
- *
The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
- * {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
- * front camera, it will be set to true; for back camera, it will be set to false.
- *
- * @param outputFileOptions Options to store the newly captured image.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor,
- @NonNull OnImageSavedCallback callback) {
- mCameraModule.takePicture(outputFileOptions, executor, callback);
- }
-
- /**
- * Takes a video and calls the OnVideoSavedCallback when done.
- *
- * @param outputFileOptions Options to store the newly captured video.
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback which will receive success or failure.
- */
- public void startRecording(@NonNull VideoCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor,
- @NonNull OnVideoSavedCallback callback) {
- mCameraModule.startRecording(outputFileOptions, executor, callback);
- }
-
- /** Stops an in progress video. */
- public void stopRecording() {
- mCameraModule.stopRecording();
- }
-
- /** @return True if currently recording. */
- public boolean isRecording() {
- return mCameraModule.isRecording();
- }
-
- /**
- * Queries whether the current device has a camera with the specified direction.
- *
- * @return True if the device supports the direction.
- * @throws IllegalStateException if the CAMERA permission is not currently granted.
- */
- @RequiresPermission(permission.CAMERA)
- public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
- return mCameraModule.hasCameraWithLensFacing(lensFacing);
- }
-
- /**
- * Toggles between the primary front facing camera and the primary back facing camera.
- *
- *
This will have no effect if not already bound to a lifecycle via {@link
- * #bindToLifecycle(LifecycleOwner)}.
- */
- public void toggleCamera() {
- mCameraModule.toggleCamera();
- }
-
- /**
- * Sets the desired camera by specifying desired lensFacing.
- *
- *
This will choose the primary camera with the specified camera lensFacing.
- *
- *
If called before {@link #bindToLifecycle(LifecycleOwner)}, this will set the camera to be
- * used when first bound to the lifecycle. If the specified lensFacing is not supported by the
- * device, as determined by {@link #hasCameraWithLensFacing(int)}, the first supported
- * lensFacing will be chosen when {@link #bindToLifecycle(LifecycleOwner)} is called.
- *
- *
If called with {@code null} AFTER binding to the lifecycle, the behavior would be
- * equivalent to unbind the use cases without the lifecycle having to be destroyed.
- *
- * @param lensFacing The desired camera lensFacing.
- */
- public void setCameraLensFacing(@Nullable Integer lensFacing) {
- mCameraModule.setCameraLensFacing(lensFacing);
- }
-
- /** Returns the currently selected lensFacing. */
- @Nullable
- public Integer getCameraLensFacing() {
- return mCameraModule.getLensFacing();
- }
-
- /** Gets the active flash strategy. */
- @ImageCapture.FlashMode
- public int getFlash() {
- return mCameraModule.getFlash();
- }
-
- // Begin Signal Custom Code Block
- public boolean hasFlash() {
- return mCameraModule.hasFlash();
- }
- // End Signal Custom Code Block
-
- /** Sets the active flash strategy. */
- public void setFlash(@ImageCapture.FlashMode int flashMode) {
- mCameraModule.setFlash(flashMode);
- }
-
- private long delta() {
- return System.currentTimeMillis() - mDownEventTimestamp;
- }
-
- @Override
- public boolean onTouchEvent(@NonNull MotionEvent event) {
- // Disable pinch-to-zoom and tap-to-focus while the camera module is paused.
- if (mCameraModule.isPaused()) {
- return false;
- }
- // Only forward the event to the pinch-to-zoom gesture detector when pinch-to-zoom is
- // enabled.
- if (isPinchToZoomEnabled()) {
- mPinchToZoomGestureDetector.onTouchEvent(event);
- }
- if (event.getPointerCount() == 2 && isPinchToZoomEnabled() && isZoomSupported()) {
- return true;
- }
-
- // Camera focus
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDownEventTimestamp = System.currentTimeMillis();
- break;
- case MotionEvent.ACTION_UP:
- if (delta() < ViewConfiguration.getLongPressTimeout()
- && mCameraModule.isBoundToLifecycle()) {
- mUpEvent = event;
- performClick();
- }
- break;
- default:
- // Unhandled event.
- return false;
- }
- return true;
- }
-
- /**
- * Focus the position of the touch event, or focus the center of the preview for
- * accessibility events
- */
- @Override
- public boolean performClick() {
- super.performClick();
-
- final float x = (mUpEvent != null) ? mUpEvent.getX() : getX() + getWidth() / 2f;
- final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
- mUpEvent = null;
-
- Camera camera = mCameraModule.getCamera();
- if (camera != null) {
- MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
- float afPointWidth = 1.0f / 6.0f; // 1/6 total area
- float aePointWidth = afPointWidth * 1.5f;
- MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
- MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
-
- ListenableFuture future =
- camera.getCameraControl().startFocusAndMetering(
- new FocusMeteringAction.Builder(afPoint,
- FocusMeteringAction.FLAG_AF).addPoint(aePoint,
- FocusMeteringAction.FLAG_AE).build());
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable FocusMeteringResult result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
-
- } else {
- Logger.d(TAG, "cannot access camera");
- }
-
- return true;
- }
-
- float rangeLimit(float val, float max, float min) {
- return Math.min(Math.max(val, min), max);
- }
-
- /**
- * Returns whether the view allows pinch-to-zoom.
- *
- * @return True if pinch to zoom is enabled.
- */
- public boolean isPinchToZoomEnabled() {
- return mIsPinchToZoomEnabled;
- }
-
- /**
- * Sets whether the view should allow pinch-to-zoom.
- *
- * When enabled, the user can pinch the camera to zoom in/out. This only has an effect if the
- * bound camera supports zoom.
- *
- * @param enabled True to enable pinch-to-zoom.
- */
- public void setPinchToZoomEnabled(boolean enabled) {
- mIsPinchToZoomEnabled = enabled;
- }
-
- /**
- * Returns the current zoom ratio.
- *
- * @return The current zoom ratio.
- */
- public float getZoomRatio() {
- return mCameraModule.getZoomRatio();
- }
-
- /**
- * Sets the current zoom ratio.
- *
- *
Valid zoom values range from {@link #getMinZoomRatio()} to {@link #getMaxZoomRatio()}.
- *
- * @param zoomRatio The requested zoom ratio.
- */
- public void setZoomRatio(float zoomRatio) {
- mCameraModule.setZoomRatio(zoomRatio);
- }
-
- /**
- * Returns the minimum zoom ratio.
- *
- *
For most cameras this should return a zoom ratio of 1. A zoom ratio of 1 corresponds to a
- * non-zoomed image.
- *
- * @return The minimum zoom ratio.
- */
- public float getMinZoomRatio() {
- return mCameraModule.getMinZoomRatio();
- }
-
- /**
- * Returns the maximum zoom ratio.
- *
- *
The zoom ratio corresponds to the ratio between both the widths and heights of a
- * non-zoomed image and a maximally zoomed image for the selected camera.
- *
- * @return The maximum zoom ratio.
- */
- public float getMaxZoomRatio() {
- return mCameraModule.getMaxZoomRatio();
- }
-
- /**
- * Returns whether the bound camera supports zooming.
- *
- * @return True if the camera supports zooming.
- */
- public boolean isZoomSupported() {
- return mCameraModule.isZoomSupported();
- }
-
- /**
- * Turns on/off torch.
- *
- * @param torch True to turn on torch, false to turn off torch.
- */
- public void enableTorch(boolean torch) {
- mCameraModule.enableTorch(torch);
- }
-
- /**
- * Returns current torch status.
- *
- * @return true if torch is on , otherwise false
- */
- public boolean isTorchOn() {
- return mCameraModule.isTorchOn();
- }
-
- /**
- * The capture mode used by CameraView.
- *
- *
This enum can be used to determine which capture mode will be enabled for {@link
- * SignalCameraView}.
- */
- public enum CaptureMode {
- /** A mode where image capture is enabled. */
- IMAGE(0),
- /** A mode where video capture is enabled. */
- VIDEO(1),
- /**
- * A mode where both image capture and video capture are simultaneously enabled. Note that
- * this mode may not be available on every device.
- */
- MIXED(2);
-
- private final int mId;
-
- int getId() {
- return mId;
- }
-
- CaptureMode(int id) {
- mId = id;
- }
-
- static CaptureMode fromId(int id) {
- for (CaptureMode f : values()) {
- if (f.mId == id) {
- return f;
- }
- }
- throw new IllegalArgumentException();
- }
- }
-
- static class S extends ScaleGestureDetector.SimpleOnScaleGestureListener {
- private ScaleGestureDetector.OnScaleGestureListener mListener;
-
- void setRealGestureDetector(ScaleGestureDetector.OnScaleGestureListener l) {
- mListener = l;
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- return mListener.onScale(detector);
- }
- }
-
- private class PinchToZoomGestureDetector extends ScaleGestureDetector
- implements ScaleGestureDetector.OnScaleGestureListener {
- PinchToZoomGestureDetector(Context context) {
- this(context, new S());
- }
-
- PinchToZoomGestureDetector(Context context, S s) {
- super(context, s);
- s.setRealGestureDetector(this);
- }
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- float scale = detector.getScaleFactor();
-
- // Speeding up the zoom by 2X.
- if (scale > 1f) {
- scale = 1.0f + (scale - 1.0f) * 2;
- } else {
- scale = 1.0f - (1.0f - scale) * 2;
- }
-
- float newRatio = getZoomRatio() * scale;
- newRatio = rangeLimit(newRatio, getMaxZoomRatio(), getMinZoomRatio());
- setZoomRatio(newRatio);
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/androidx/camera/view/SignalCameraXModule.java b/app/src/main/java/androidx/camera/view/SignalCameraXModule.java
deleted file mode 100644
index 4cad6d5adb..0000000000
--- a/app/src/main/java/androidx/camera/view/SignalCameraXModule.java
+++ /dev/null
@@ -1,699 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.view;
-
-import android.Manifest.permission;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Rational;
-import android.util.Size;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RequiresPermission;
-import androidx.camera.core.Camera;
-import androidx.camera.core.CameraInfoUnavailableException;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
-import androidx.camera.core.ImageCapture.OnImageSavedCallback;
-import androidx.camera.core.Logger;
-import androidx.camera.core.Preview;
-import androidx.camera.core.TorchState;
-import androidx.camera.core.UseCase;
-import androidx.camera.core.VideoCapture;
-import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
-import androidx.camera.core.impl.CameraInternal;
-import androidx.camera.core.impl.LensFacingConverter;
-import androidx.camera.core.impl.utils.CameraOrientationUtil;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.core.util.Consumer;
-import androidx.core.util.Preconditions;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
-import org.thoughtcrime.securesms.mms.MediaConstraints;
-import org.thoughtcrime.securesms.video.VideoUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
-
-/** CameraX use case operation built on @{link androidx.camera.core}. */
-@RequiresApi(21)
-@SuppressLint("RestrictedApi")
-final class SignalCameraXModule {
- public static final String TAG = "CameraXModule";
-
- private static final float UNITY_ZOOM_SCALE = 1f;
- private static final float ZOOM_NOT_SUPPORTED = UNITY_ZOOM_SCALE;
- private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
- private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
- private static final Rational ASPECT_RATIO_9_16 = new Rational(9, 16);
- private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
-
- private final Preview.Builder mPreviewBuilder;
- private final VideoCapture.Builder mVideoCaptureBuilder;
- private final ImageCapture.Builder mImageCaptureBuilder;
- private final SignalCameraView mCameraView;
- final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
- private SignalCameraView.CaptureMode mCaptureMode = SignalCameraView.CaptureMode.IMAGE;
- private long mMaxVideoDuration = SignalCameraView.INDEFINITE_VIDEO_DURATION;
- private long mMaxVideoSize = SignalCameraView.INDEFINITE_VIDEO_SIZE;
- @ImageCapture.FlashMode
- private int mFlash = FLASH_MODE_OFF;
- @Nullable
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- Camera mCamera;
- @Nullable
- private ImageCapture mImageCapture;
- @Nullable
- private VideoCapture mVideoCapture;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- Preview mPreview;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- LifecycleOwner mCurrentLifecycle;
- private final LifecycleObserver mCurrentLifecycleObserver =
- new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- public void onDestroy(LifecycleOwner owner) {
- if (owner == mCurrentLifecycle) {
- clearCurrentLifecycle();
- }
- }
- };
- @Nullable
- private LifecycleOwner mNewLifecycle;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- Integer mCameraLensFacing = CameraSelector.LENS_FACING_BACK;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @Nullable
- ProcessCameraProvider mCameraProvider;
-
- // BEGIN Custom Signal Code Block
- SignalCameraXModule(SignalCameraView view, Consumer errorConsumer) {
- // END Custom Signal Code Block
- mCameraView = view;
-
- Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
- new FutureCallback() {
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- @Override
- public void onSuccess(@Nullable ProcessCameraProvider provider) {
- Preconditions.checkNotNull(provider);
- mCameraProvider = provider;
- if (mCurrentLifecycle != null) {
- bindToLifecycle(mCurrentLifecycle);
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- // BEGIN Custom Signal Code Block
- errorConsumer.accept(t);
- // END Custom Signal Code Block
- }
- }, CameraXExecutors.mainThreadExecutor());
-
- mPreviewBuilder = new Preview.Builder().setTargetName("Preview");
-
- mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
-
- mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture")
- .setAudioBitRate(VideoUtil.AUDIO_BIT_RATE)
- .setVideoFrameRate(VideoUtil.VIDEO_FRAME_RATE)
- .setBitRate(VideoUtil.VIDEO_BIT_RATE);
- }
-
- @RequiresPermission(permission.CAMERA)
- void bindToLifecycle(LifecycleOwner lifecycleOwner) {
- mNewLifecycle = lifecycleOwner;
-
- if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
- bindToLifecycleAfterViewMeasured();
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- void bindToLifecycleAfterViewMeasured() {
- if (mNewLifecycle == null) {
- return;
- }
-
- clearCurrentLifecycle();
- if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
- // Lifecycle is already in a destroyed state. Since it may have been a valid
- // lifecycle when bound, but became destroyed while waiting for layout, treat this as
- // a no-op now that we have cleared the previous lifecycle.
- mNewLifecycle = null;
- return;
- }
- mCurrentLifecycle = mNewLifecycle;
- mNewLifecycle = null;
-
- if (mCameraProvider == null) {
- // try again once the camera provider is no longer null
- return;
- }
-
- Set available = getAvailableCameraLensFacing();
-
- if (available.isEmpty()) {
- Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
- mCameraLensFacing = null;
- }
-
- // Ensure the current camera exists, or default to another camera
- if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
- Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
-
- // Default to the first available camera direction
- mCameraLensFacing = available.iterator().next();
-
- Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
- }
-
- // Do not attempt to create use cases for a null cameraLensFacing. This could occur if
- // the user explicitly sets the LensFacing to null, or if we determined there
- // were no available cameras, which should be logged in the logic above.
- if (mCameraLensFacing == null) {
- return;
- }
-
- // Set the preferred aspect ratio as 4:3 if it is IMAGE only mode. Set the preferred aspect
- // ratio as 16:9 if it is VIDEO or MIXED mode. Then, it will be WYSIWYG when the view finder
- // is in CENTER_INSIDE mode.
-
- boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
- || getDisplayRotationDegrees() == 180;
-
- // Begin Signal Custom Code Block
- int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
- // End Signal Custom Code Block
-
- Rational targetAspectRatio;
- // Begin Signal Custom Code Block
- mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, isDisplayPortrait));
- targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
- // End Signal Custom Code Block
-
- // Begin Signal Custom Code Block
- mImageCaptureBuilder.setCaptureMode(CameraXUtil.getOptimalCaptureMode());
- // End Signal Custom Code Block
-
- mImageCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
- mImageCapture = mImageCaptureBuilder.build();
-
- // Begin Signal Custom Code Block
- Size size = VideoUtil.getVideoRecordingSize();
- mVideoCaptureBuilder.setTargetResolution(size);
- mVideoCaptureBuilder.setMaxResolution(size);
- // End Signal Custom Code Block
-
- mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
- // Begin Signal Custom Code Block
- if (MediaConstraints.isVideoTranscodeAvailable()) {
- mVideoCapture = mVideoCaptureBuilder.build();
- }
- // End Signal Custom Code Block
-
- // Adjusts the preview resolution according to the view size and the target aspect ratio.
- int height = (int) (getMeasuredWidth() / targetAspectRatio.floatValue());
- mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
-
- mPreview = mPreviewBuilder.build();
- mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
-
- CameraSelector cameraSelector =
- new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
- if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mImageCapture,
- mPreview);
- } else if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mVideoCapture,
- mPreview);
- } else {
- mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
- mImageCapture,
- mVideoCapture, mPreview);
- }
-
- setZoomRatio(UNITY_ZOOM_SCALE);
- mCurrentLifecycle.getLifecycle().addObserver(mCurrentLifecycleObserver);
- // Enable flash setting in ImageCapture after use cases are created and binded.
- setFlash(getFlash());
- }
-
- public void open() {
- throw new UnsupportedOperationException(
- "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
- }
-
- public void close() {
- throw new UnsupportedOperationException(
- "Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
- }
-
- public void takePicture(Executor executor, OnImageCapturedCallback callback) {
- if (mImageCapture == null) {
- return;
- }
-
- if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
- throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnImageCapturedCallback should not be empty");
- }
-
- mImageCapture.takePicture(executor, callback);
- }
-
- public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
- @NonNull Executor executor, OnImageSavedCallback callback) {
- if (mImageCapture == null) {
- return;
- }
-
- if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
- throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
- }
-
- outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
- && mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
- mImageCapture.takePicture(outputFileOptions, executor, callback);
- }
-
- public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
- Executor executor, final OnVideoSavedCallback callback) {
- if (mVideoCapture == null) {
- return;
- }
-
- if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
- throw new IllegalStateException("Can not record video under IMAGE capture mode.");
- }
-
- if (callback == null) {
- throw new IllegalArgumentException("OnVideoSavedCallback should not be empty");
- }
-
- mVideoIsRecording.set(true);
- mVideoCapture.startRecording(
- outputFileOptions,
- executor,
- new VideoCapture.OnVideoSavedCallback() {
- @Override
- public void onVideoSaved(
- @NonNull VideoCapture.OutputFileResults outputFileResults) {
- mVideoIsRecording.set(false);
- callback.onVideoSaved(outputFileResults);
- }
-
- @Override
- public void onError(
- @VideoCapture.VideoCaptureError int videoCaptureError,
- @NonNull String message,
- @Nullable Throwable cause) {
- mVideoIsRecording.set(false);
- Logger.e(TAG, message, cause);
- callback.onError(videoCaptureError, message, cause);
- }
- });
- }
-
- public void stopRecording() {
- if (mVideoCapture == null) {
- return;
- }
-
- mVideoCapture.stopRecording();
- }
-
- public boolean isRecording() {
- return mVideoIsRecording.get();
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- public void setCameraLensFacing(@Nullable Integer lensFacing) {
- // Setting same lens facing is a no-op, so check for that first
- if (!Objects.equals(mCameraLensFacing, lensFacing)) {
- // If we're not bound to a lifecycle, just update the camera that will be opened when we
- // attach to a lifecycle.
- mCameraLensFacing = lensFacing;
-
- if (mCurrentLifecycle != null) {
- // Re-bind to lifecycle with new camera
- bindToLifecycle(mCurrentLifecycle);
- }
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
- if (mCameraProvider == null) {
- return false;
- }
- try {
- return mCameraProvider.hasCamera(
- new CameraSelector.Builder().requireLensFacing(lensFacing).build());
- } catch (CameraInfoUnavailableException e) {
- return false;
- }
- }
-
- @Nullable
- public Integer getLensFacing() {
- return mCameraLensFacing;
- }
-
- public void toggleCamera() {
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- Set availableCameraLensFacing = getAvailableCameraLensFacing();
-
- if (availableCameraLensFacing.isEmpty()) {
- return;
- }
-
- if (mCameraLensFacing == null) {
- setCameraLensFacing(availableCameraLensFacing.iterator().next());
- return;
- }
-
- if (mCameraLensFacing == CameraSelector.LENS_FACING_BACK
- && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_FRONT)) {
- setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
- return;
- }
-
- if (mCameraLensFacing == CameraSelector.LENS_FACING_FRONT
- && availableCameraLensFacing.contains(CameraSelector.LENS_FACING_BACK)) {
- setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
- return;
- }
- }
-
- public float getZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio();
- } else {
- return UNITY_ZOOM_SCALE;
- }
- }
-
- public void setZoomRatio(float zoomRatio) {
- if (mCamera != null) {
- ListenableFuture future = mCamera.getCameraControl().setZoomRatio(
- zoomRatio);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
- } else {
- Logger.e(TAG, "Failed to set zoom ratio");
- }
- }
-
- public float getMinZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
- } else {
- return UNITY_ZOOM_SCALE;
- }
- }
-
- public float getMaxZoomRatio() {
- if (mCamera != null) {
- return mCamera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
- } else {
- return ZOOM_NOT_SUPPORTED;
- }
- }
-
- public boolean isZoomSupported() {
- return getMaxZoomRatio() != ZOOM_NOT_SUPPORTED;
- }
-
- // TODO(b/124269166): Rethink how we can handle permissions here.
- @SuppressLint("MissingPermission")
- private void rebindToLifecycle() {
- if (mCurrentLifecycle != null) {
- bindToLifecycle(mCurrentLifecycle);
- }
- }
-
- boolean isBoundToLifecycle() {
- return mCamera != null;
- }
-
- int getRelativeCameraOrientation(boolean compensateForMirroring) {
- int rotationDegrees = 0;
- if (mCamera != null) {
- rotationDegrees =
- mCamera.getCameraInfo().getSensorRotationDegrees(getDisplaySurfaceRotation());
- if (compensateForMirroring) {
- rotationDegrees = (360 - rotationDegrees) % 360;
- }
- }
-
- return rotationDegrees;
- }
-
- @SuppressLint("UnsafeExperimentalUsageError")
- public void invalidateView() {
- if (mPreview != null) {
- mPreview.setTargetRotation(getDisplaySurfaceRotation()); // Fixes issue #10940 (rotation not updated on phones using "Legacy API")
- }
-
- updateViewInfo();
- }
-
- void clearCurrentLifecycle() {
- if (mCurrentLifecycle != null && mCameraProvider != null) {
- // Remove previous use cases
- List toUnbind = new ArrayList<>();
- if (mImageCapture != null && mCameraProvider.isBound(mImageCapture)) {
- toUnbind.add(mImageCapture);
- }
- if (mVideoCapture != null && mCameraProvider.isBound(mVideoCapture)) {
- toUnbind.add(mVideoCapture);
- }
- if (mPreview != null && mCameraProvider.isBound(mPreview)) {
- toUnbind.add(mPreview);
- }
-
- if (!toUnbind.isEmpty()) {
- mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
- }
-
- // Remove surface provider once unbound.
- if (mPreview != null) {
- mPreview.setSurfaceProvider(null);
- }
- }
- mCamera = null;
- mCurrentLifecycle = null;
- }
-
- // Update view related information used in use cases
- private void updateViewInfo() {
- if (mImageCapture != null) {
- mImageCapture.setCropAspectRatio(new Rational(getWidth(), getHeight()));
- mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
- }
-
- if (mVideoCapture != null) {
- mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
- }
- }
-
- @RequiresPermission(permission.CAMERA)
- private Set getAvailableCameraLensFacing() {
- // Start with all camera directions
- Set available = new LinkedHashSet<>(Arrays.asList(LensFacingConverter.values()));
-
- // If we're bound to a lifecycle, remove unavailable cameras
- if (mCurrentLifecycle != null) {
- if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)) {
- available.remove(CameraSelector.LENS_FACING_BACK);
- }
-
- if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT)) {
- available.remove(CameraSelector.LENS_FACING_FRONT);
- }
- }
-
- return available;
- }
-
- @ImageCapture.FlashMode
- public int getFlash() {
- return mFlash;
- }
-
- // Begin Signal Custom Code Block
- public boolean hasFlash() {
- if (mImageCapture == null) {
- return false;
- }
-
- CameraInternal camera = mImageCapture.getCamera();
-
- if (camera == null) {
- return false;
- }
-
- return camera.getCameraInfoInternal().hasFlashUnit();
- }
- // End Signal Custom Code Block
-
- public void setFlash(@ImageCapture.FlashMode int flash) {
- this.mFlash = flash;
-
- if (mImageCapture == null) {
- // Do nothing if there is no imageCapture
- return;
- }
-
- mImageCapture.setFlashMode(flash);
- }
-
- public void enableTorch(boolean torch) {
- if (mCamera == null) {
- return;
- }
- ListenableFuture future = mCamera.getCameraControl().enableTorch(torch);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void result) {
- }
-
- @Override
- public void onFailure(Throwable t) {
- // Throw the unexpected error.
- throw new RuntimeException(t);
- }
- }, CameraXExecutors.directExecutor());
- }
-
- public boolean isTorchOn() {
- if (mCamera == null) {
- return false;
- }
- return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON;
- }
-
- public Context getContext() {
- return mCameraView.getContext();
- }
-
- public int getWidth() {
- return mCameraView.getWidth();
- }
-
- public int getHeight() {
- return mCameraView.getHeight();
- }
-
- public int getDisplayRotationDegrees() {
- return CameraOrientationUtil.surfaceRotationToDegrees(getDisplaySurfaceRotation());
- }
-
- protected int getDisplaySurfaceRotation() {
- return mCameraView.getDisplaySurfaceRotation();
- }
-
- private int getMeasuredWidth() {
- return mCameraView.getMeasuredWidth();
- }
-
- private int getMeasuredHeight() {
- return mCameraView.getMeasuredHeight();
- }
-
- @Nullable
- public Camera getCamera() {
- return mCamera;
- }
-
- @NonNull
- public SignalCameraView.CaptureMode getCaptureMode() {
- return mCaptureMode;
- }
-
- public void setCaptureMode(@NonNull SignalCameraView.CaptureMode captureMode) {
- this.mCaptureMode = captureMode;
- rebindToLifecycle();
- }
-
- public long getMaxVideoDuration() {
- return mMaxVideoDuration;
- }
-
- public void setMaxVideoDuration(long duration) {
- mMaxVideoDuration = duration;
- }
-
- public long getMaxVideoSize() {
- return mMaxVideoSize;
- }
-
- public void setMaxVideoSize(long size) {
- mMaxVideoSize = size;
- }
-
- public boolean isPaused() {
- return false;
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java
index e988a19373..e443f12871 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java
@@ -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();
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 f6d61bc91c..c93c6b0b56 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java
@@ -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);
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 6248002ced..6e7f310337 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXSelfieFlashHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXSelfieFlashHelper.java
@@ -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;
}
}
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 9cdb7f663f..54e563818e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXVideoCaptureHelper.java
@@ -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 {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXFlashToggleView.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXFlashToggleView.java
index 828b47d392..aff3d2d0e7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXFlashToggleView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/camerax/CameraXFlashToggleView.java
@@ -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";
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 45b6cd2232..0caf1153d3 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
@@ -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;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MemoryFileDescriptor.java b/app/src/main/java/org/thoughtcrime/securesms/util/MemoryFileDescriptor.java
index f75902a2b2..a7b68aaf33 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/MemoryFileDescriptor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/MemoryFileDescriptor.java
@@ -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);
diff --git a/app/src/main/res/layout/camerax_fragment.xml b/app/src/main/res/layout/camerax_fragment.xml
index 8d74bf0bdc..ab88cea34f 100644
--- a/app/src/main/res/layout/camerax_fragment.xml
+++ b/app/src/main/res/layout/camerax_fragment.xml
@@ -12,12 +12,12 @@
app:cardElevation="0dp"
app:layout_constraintTop_toTopOf="parent">
-
+ app:implementationMode="compatible" />
+
+
+
+
+
+
+
+
@@ -158,36 +166,36 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
@@ -396,8 +404,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
-
-
+
+
+
+
+
+
+
diff --git a/qr/lib/src/main/java/org/signal/qr/ImageProxyLuminanceSource.kt b/qr/lib/src/main/java/org/signal/qr/ImageProxyLuminanceSource.kt
index f9e721ff13..470b3dd60d 100644
--- a/qr/lib/src/main/java/org/signal/qr/ImageProxyLuminanceSource.kt
+++ b/qr/lib/src/main/java/org/signal/qr/ImageProxyLuminanceSource.kt
@@ -1,6 +1,7 @@
package org.signal.qr
import android.graphics.ImageFormat
+import androidx.annotation.RequiresApi
import androidx.camera.core.ImageProxy
import com.google.zxing.LuminanceSource
import java.nio.ByteBuffer
@@ -13,6 +14,7 @@ import java.nio.ByteBuffer
* An image width can be reported as 1080 but the row stride may be 1088. Thus when representing a row-major
* 2D array as a 1D array, the math can go sideways if width is used instead of row stride.
*/
+@RequiresApi(21)
class ImageProxyLuminanceSource(image: ImageProxy) : LuminanceSource(image.width, image.height) {
val yData: ByteArray
diff --git a/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt b/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt
index 8462d81ca6..da1e982dff 100644
--- a/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt
+++ b/qr/lib/src/main/java/org/signal/qr/QrProcessor.kt
@@ -1,5 +1,6 @@
package org.signal.qr
+import androidx.annotation.RequiresApi
import androidx.camera.core.ImageProxy
import com.google.zxing.BinaryBitmap
import com.google.zxing.ChecksumException
@@ -23,6 +24,7 @@ class QrProcessor {
private var previousHeight = 0
private var previousWidth = 0
+ @RequiresApi(21)
fun getScannedData(proxy: ImageProxy): String? {
return getScannedData(ImageProxyLuminanceSource(proxy))
}