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)) }