Upgrade CameraX to 1.1.0 and fork removal.

fork-5.53.8
Alex Hart 2022-07-27 10:53:04 -03:00 zatwierdzone przez Greyson Parrelli
rodzic e3e9f90094
commit a52b64281c
14 zmienionych plików z 151 dodań i 1667 usunięć

Wyświetl plik

@ -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()}.
*
* <p>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<Throwable> 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.
*
* <p>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.
*
* <p>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<Throwable> 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<PreviewView.StreamState> 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.
*
* <p>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
*
* <p>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.
*
* <p> 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.
*
* <p>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.
*
* <p>This will choose the primary camera with the specified camera lensFacing.
*
* <p>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.
*
* <p>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<FocusMeteringResult> future =
camera.getCameraControl().startFocusAndMetering(
new FocusMeteringAction.Builder(afPoint,
FocusMeteringAction.FLAG_AF).addPoint(aePoint,
FocusMeteringAction.FLAG_AE).build());
Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
@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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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) {
}
}
}

Wyświetl plik

@ -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<Throwable> errorConsumer) {
// END Custom Signal Code Block
mCameraView = view;
Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
new FutureCallback<ProcessCameraProvider>() {
// 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<Integer> 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<Integer> 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<Void> future = mCamera.getCameraControl().setZoomRatio(
zoomRatio);
Futures.addCallback(future, new FutureCallback<Void>() {
@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<UseCase> 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<Integer> getAvailableCameraLensFacing() {
// Start with all camera directions
Set<Integer> 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<Void> future = mCamera.getCameraControl().enableTorch(torch);
Futures.addCallback(future, new FutureCallback<Void>() {
@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;
}
}

Wyświetl plik

@ -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();

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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";

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -12,12 +12,12 @@
app:cardElevation="0dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.camera.view.SignalCameraView
<androidx.camera.view.PreviewView
android:id="@+id/camerax_camera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
app:lensFacing="back" />
app:implementationMode="compatible" />
</androidx.cardview.widget.CardView>
<FrameLayout

Wyświetl plik

@ -6,7 +6,7 @@ dependencyResolutionManagement {
libs {
version('libsignal-client', '0.18.1')
version('exoplayer', '2.15.0')
version('androidx-camera', '1.0.0-beta11')
version('androidx-camera', '1.1.0')
version('androidx-lifecycle', '2.3.1')
version('mp4parser', '1.9.39')
version('kotlin', '1.6.21')
@ -42,7 +42,7 @@ dependencyResolutionManagement {
alias('androidx-camera-core').to('androidx.camera', 'camera-core').versionRef('androidx-camera')
alias('androidx-camera-camera2').to('androidx.camera', 'camera-camera2').versionRef('androidx-camera')
alias('androidx-camera-lifecycle').to('androidx.camera', 'camera-lifecycle').versionRef('androidx-camera')
alias('androidx-camera-view').to('androidx.camera:camera-view:1.0.0-alpha18')
alias('androidx-camera-view').to('androidx.camera', 'camera-view').versionRef('androidx-camera')
alias('androidx-concurrent-futures').to('androidx.concurrent:concurrent-futures:1.0.0')
alias('androidx-autofill').to('androidx.autofill:autofill:1.0.0')
alias('androidx-biometric').to('androidx.biometric:biometric:1.1.0')

Wyświetl plik

@ -86,6 +86,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="60825c4e54e4b0ceabded342eca1e7d59dc08d703b5345985a85f184f93a1e0a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-experimental" version="1.1.0">
<artifact name="annotation-experimental-1.1.0.aar">
<sha256 value="0157de61a2064047896a058080f3fd67ba57ad9a94857b3f7a363660243e3f90" origin="Generated by Gradle"/>
</artifact>
<artifact name="annotation-experimental-1.1.0.module">
<sha256 value="0361d1526a4d7501255e19779e09e93cdbd07fee0e2f5c50b7a137432d510119" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.appcompat" name="appcompat" version="1.2.0">
<artifact name="appcompat-1.2.0.aar">
<sha256 value="3d2131a55a61a777322e2126e0018011efa6339e53b44153eb651b16020cca70" origin="Generated by Gradle"/>
@ -158,36 +166,36 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<sha256 value="4999f205b2b38e31180ad58111e32d564190c2024fbfd58527e2da8bfb67ff65" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.camera" name="camera-camera2" version="1.0.0-beta11">
<artifact name="camera-camera2-1.0.0-beta11.aar">
<sha256 value="54d7c975ea7387f0d7c65faf531005fc543fe8e8d826eb696bb2c7f950041a9c" origin="Generated by Gradle"/>
<component group="androidx.camera" name="camera-camera2" version="1.1.0">
<artifact name="camera-camera2-1.1.0.aar">
<sha256 value="abf07c23f6be963fe36943ce4f1380711ad22bad32b6e62bdddd160ad7d9de22" origin="Generated by Gradle"/>
</artifact>
<artifact name="camera-camera2-1.0.0-beta11.module">
<sha256 value="d18d884f8d6839f71fa15076fa538b0be02e64e0f1b10241c86aa23dec81e956" origin="Generated by Gradle"/>
<artifact name="camera-camera2-1.1.0.module">
<sha256 value="5b3e2d4df16b1506cba137af7d36b7f10ec295941da679d331d0220db7b8b753" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.camera" name="camera-core" version="1.0.0-beta11">
<artifact name="camera-core-1.0.0-beta11.aar">
<sha256 value="58ed18e385d3eea3e17568b5904629f5111de8af87035942f908d49815331645" origin="Generated by Gradle"/>
<component group="androidx.camera" name="camera-core" version="1.1.0">
<artifact name="camera-core-1.1.0.aar">
<sha256 value="c6fae68081d9f3344c4ae2b39d095d2f6d7f670f9f4b37e54574d3183cbf9f08" origin="Generated by Gradle"/>
</artifact>
<artifact name="camera-core-1.0.0-beta11.module">
<sha256 value="cbfd2839b2d5f3a76d4f789bd3e70b9950d8872b2efe5d946f5c31bc197a2139" origin="Generated by Gradle"/>
<artifact name="camera-core-1.1.0.module">
<sha256 value="adbb9571f8ed5e38e9751cf9275bcabede7592c3bd009753d10b703dcc7106a1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.camera" name="camera-lifecycle" version="1.0.0-beta11">
<artifact name="camera-lifecycle-1.0.0-beta11.aar">
<sha256 value="f8617971cbfb2d249eea3558756bc466cb35a946a9269b966b24b6787f449631" origin="Generated by Gradle"/>
<component group="androidx.camera" name="camera-lifecycle" version="1.1.0">
<artifact name="camera-lifecycle-1.1.0.aar">
<sha256 value="9dc468572a73627ae403e3a9ffaad71bcfc00af9d02aa57aba5a5726d06246b2" origin="Generated by Gradle"/>
</artifact>
<artifact name="camera-lifecycle-1.0.0-beta11.module">
<sha256 value="28e7356c1a5c3bbf2a2d91168a0576e91f0e01e39a724c34fb2a01ee44219f69" origin="Generated by Gradle"/>
<artifact name="camera-lifecycle-1.1.0.module">
<sha256 value="b2ae8a62b552baeaaffc1ff7fca9884ff70bd0c389928944fe85d36fe52362a2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.camera" name="camera-view" version="1.0.0-alpha18">
<artifact name="camera-view-1.0.0-alpha18.aar">
<sha256 value="029652f71430e4acc97c41d40e509bf6774a86dd35a19cea5c856f0b96a6c70c" origin="Generated by Gradle"/>
<component group="androidx.camera" name="camera-view" version="1.1.0">
<artifact name="camera-view-1.1.0.aar">
<sha256 value="612de419931de4bb2b3d6f4f69435759211da9751199a36a52664b28a25bda8c" origin="Generated by Gradle"/>
</artifact>
<artifact name="camera-view-1.0.0-alpha18.module">
<sha256 value="481fa03b781b6396f12de88777125a71808e528b8c6ecc0233bb6ad46c9c1206" origin="Generated by Gradle"/>
<artifact name="camera-view-1.1.0.module">
<sha256 value="7ff023c7a0b6ab89af4ffd57420e7d63bcd4b7567b2140ffe9ad941a95fb2d9a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.cardview" name="cardview" version="1.0.0">
@ -396,8 +404,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
<artifact name="exifinterface-1.0.0.aar">
<sha256 value="ee48be10aab8f54efff4c14b77d11e10b9eeee4379d5ef6bf297a2923c55cc11" origin="Generated by Gradle"/>
</artifact>
<artifact name="exifinterface-1.0.0.pom">
<sha256 value="ee15ccb739fa8ab72e48649e53a3def0ee2d5a110736018e781df00437fbf2b3" origin="Generated by Gradle"/>
</component>
<component group="androidx.exifinterface" name="exifinterface" version="1.3.2">
<artifact name="exifinterface-1.3.2.aar">
<sha256 value="8770c180103e0b8c04a07eb4c59153af639b09eca25deae9bdcdaf869d1e5b6b" origin="Generated by Gradle"/>
</artifact>
<artifact name="exifinterface-1.3.2.module">
<sha256 value="10ba5b5cbea7f5c8758be4fdaec60a3545e891a1130d830a442b88cf5336a885" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment" version="1.0.0">

Wyświetl plik

@ -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

Wyświetl plik

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