mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-27 06:29:54 +00:00
Upgrade CameraX to 1.1.0 and fork removal.
This commit is contained in:
committed by
Greyson Parrelli
parent
e3e9f90094
commit
a52b64281c
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user