kopia lustrzana https://github.com/ryukoposting/Signal-Android
Migrate all QR scanning to new scanner.
rodzic
caf1329005
commit
18eac51576
|
@ -57,7 +57,7 @@ public class DeviceAddFragment extends LoggingFragment {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
scannerView.start(getViewLifecycleOwner(), FeatureFlags.useQrLegacyScan());
|
scannerView.start(getViewLifecycleOwner());
|
||||||
|
|
||||||
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
||||||
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.components.camera;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.SurfaceHolder;
|
|
||||||
import android.view.SurfaceView;
|
|
||||||
|
|
||||||
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
|
|
||||||
private boolean ready;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public CameraSurfaceView(Context context) {
|
|
||||||
super(context);
|
|
||||||
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
|
||||||
getHolder().addCallback(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isReady() {
|
|
||||||
return ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
|
||||||
ready = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
||||||
ready = false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.components.camera;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.hardware.Camera;
|
|
||||||
import android.hardware.Camera.CameraInfo;
|
|
||||||
import android.hardware.Camera.Parameters;
|
|
||||||
import android.hardware.Camera.Size;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.Surface;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public class CameraUtils {
|
|
||||||
private static final String TAG = Log.tag(CameraUtils.class);
|
|
||||||
/*
|
|
||||||
* modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
|
|
||||||
*/
|
|
||||||
public static @Nullable Size getPreferredPreviewSize(int displayOrientation,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
@NonNull Parameters parameters) {
|
|
||||||
final int targetWidth = displayOrientation % 180 == 90 ? height : width;
|
|
||||||
final int targetHeight = displayOrientation % 180 == 90 ? width : height;
|
|
||||||
final double targetRatio = (double) targetWidth / targetHeight;
|
|
||||||
|
|
||||||
Log.d(TAG, String.format(Locale.US,
|
|
||||||
"getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f",
|
|
||||||
displayOrientation, width, height,
|
|
||||||
targetWidth, targetHeight, targetRatio));
|
|
||||||
|
|
||||||
List<Size> sizes = parameters.getSupportedPreviewSizes();
|
|
||||||
List<Size> ideals = new LinkedList<>();
|
|
||||||
List<Size> bigEnough = new LinkedList<>();
|
|
||||||
|
|
||||||
for (Size size : sizes) {
|
|
||||||
Log.d(TAG, String.format(Locale.US, " %dx%d (%.02f)", size.width, size.height, (float)size.width / size.height));
|
|
||||||
|
|
||||||
if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) {
|
|
||||||
ideals.add(size);
|
|
||||||
Log.d(TAG, " (ideal ratio)");
|
|
||||||
} else if (size.width >= targetWidth && size.height >= targetHeight) {
|
|
||||||
bigEnough.add(size);
|
|
||||||
Log.d(TAG, " (good size, suboptimal ratio)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ideals.isEmpty()) return Collections.min(ideals, new AreaComparator());
|
|
||||||
else if (!bigEnough.isEmpty()) return Collections.min(bigEnough, new AspectRatioComparator(targetRatio));
|
|
||||||
else return Collections.max(sizes, new AreaComparator());
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on
|
|
||||||
// http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
|
|
||||||
// and http://stackoverflow.com/a/10383164/115145
|
|
||||||
public static int getCameraDisplayOrientation(@NonNull Activity activity,
|
|
||||||
@NonNull CameraInfo info)
|
|
||||||
{
|
|
||||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
|
||||||
int degrees = 0;
|
|
||||||
DisplayMetrics dm = new DisplayMetrics();
|
|
||||||
|
|
||||||
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
|
|
||||||
|
|
||||||
switch (rotation) {
|
|
||||||
case Surface.ROTATION_0: degrees = 0; break;
|
|
||||||
case Surface.ROTATION_90: degrees = 90; break;
|
|
||||||
case Surface.ROTATION_180: degrees = 180; break;
|
|
||||||
case Surface.ROTATION_270: degrees = 270; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
|
||||||
return (360 - ((info.orientation + degrees) % 360)) % 360;
|
|
||||||
} else {
|
|
||||||
return (info.orientation - degrees + 360) % 360;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AreaComparator implements Comparator<Size> {
|
|
||||||
@Override
|
|
||||||
public int compare(Size lhs, Size rhs) {
|
|
||||||
return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AspectRatioComparator extends AreaComparator {
|
|
||||||
private final double target;
|
|
||||||
public AspectRatioComparator(double target) {
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(Size lhs, Size rhs) {
|
|
||||||
final double lhsDiff = Math.abs(target - (double) lhs.width / lhs.height);
|
|
||||||
final double rhsDiff = Math.abs(target - (double) rhs.width / rhs.height);
|
|
||||||
if (lhsDiff < rhsDiff) return -1;
|
|
||||||
else if (lhsDiff > rhsDiff) return 1;
|
|
||||||
else return super.compare(lhs, rhs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,575 +0,0 @@
|
||||||
/***
|
|
||||||
Copyright (c) 2013-2014 CommonsWare, LLC
|
|
||||||
Portions Copyright (C) 2007 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 org.thoughtcrime.securesms.components.camera;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.hardware.Camera;
|
|
||||||
import android.hardware.Camera.CameraInfo;
|
|
||||||
import android.hardware.Camera.Parameters;
|
|
||||||
import android.hardware.Camera.Size;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Build.VERSION;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.OrientationEventListener;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.signal.core.util.ThreadUtil;
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.signal.qr.kitkat.QrCameraView;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public class CameraView extends ViewGroup {
|
|
||||||
private static final String TAG = Log.tag(CameraView.class);
|
|
||||||
|
|
||||||
private final CameraSurfaceView surface;
|
|
||||||
private final OnOrientationChange onOrientationChange;
|
|
||||||
|
|
||||||
private volatile Optional<Camera> camera = Optional.empty();
|
|
||||||
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
|
||||||
private volatile int displayOrientation = -1;
|
|
||||||
|
|
||||||
private @NonNull State state = State.PAUSED;
|
|
||||||
private @Nullable Size previewSize;
|
|
||||||
private @NonNull List<CameraViewListener> listeners = Collections.synchronizedList(new LinkedList<CameraViewListener>());
|
|
||||||
private int outputOrientation = -1;
|
|
||||||
|
|
||||||
public CameraView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CameraView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CameraView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
setBackgroundColor(Color.BLACK);
|
|
||||||
|
|
||||||
if (attrs != null) {
|
|
||||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
|
|
||||||
int camera = typedArray.getInt(R.styleable.CameraView_camera, -1);
|
|
||||||
|
|
||||||
if (camera != -1) cameraId = camera;
|
|
||||||
else if (isMultiCamera()) cameraId = TextSecurePreferences.getDirectCaptureCameraId(context);
|
|
||||||
|
|
||||||
typedArray.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
surface = new CameraSurfaceView(getContext());
|
|
||||||
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
|
|
||||||
addView(surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
|
||||||
public void onResume() {
|
|
||||||
if (state != State.PAUSED) return;
|
|
||||||
state = State.RESUMED;
|
|
||||||
Log.i(TAG, "onResume() queued");
|
|
||||||
enqueueTask(new SerialAsyncTask<Void>() {
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
@Nullable
|
|
||||||
Void onRunBackground() {
|
|
||||||
try {
|
|
||||||
long openStartMillis = System.currentTimeMillis();
|
|
||||||
camera = Optional.ofNullable(Camera.open(cameraId));
|
|
||||||
Log.i(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms");
|
|
||||||
synchronized (CameraView.this) {
|
|
||||||
CameraView.this.notifyAll();
|
|
||||||
}
|
|
||||||
if (camera.isPresent()) onCameraReady(camera.get());
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostMain(Void avoid) {
|
|
||||||
if (!camera.isPresent()) {
|
|
||||||
Log.w(TAG, "tried to open camera but got null");
|
|
||||||
for (CameraViewListener listener : listeners) {
|
|
||||||
listener.onCameraFail();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
|
||||||
onOrientationChange.enable();
|
|
||||||
}
|
|
||||||
Log.i(TAG, "onResume() completed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPause() {
|
|
||||||
if (state == State.PAUSED) return;
|
|
||||||
state = State.PAUSED;
|
|
||||||
Log.i(TAG, "onPause() queued");
|
|
||||||
|
|
||||||
enqueueTask(new SerialAsyncTask<Void>() {
|
|
||||||
private Optional<Camera> cameraToDestroy;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreMain() {
|
|
||||||
cameraToDestroy = camera;
|
|
||||||
camera = Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void onRunBackground() {
|
|
||||||
if (cameraToDestroy.isPresent()) {
|
|
||||||
try {
|
|
||||||
stopPreview();
|
|
||||||
cameraToDestroy.get().setPreviewCallback(null);
|
|
||||||
cameraToDestroy.get().release();
|
|
||||||
Log.w(TAG, "released old camera instance");
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected void onPostMain(Void avoid) {
|
|
||||||
onOrientationChange.disable();
|
|
||||||
displayOrientation = -1;
|
|
||||||
outputOrientation = -1;
|
|
||||||
removeView(surface);
|
|
||||||
addView(surface);
|
|
||||||
Log.i(TAG, "onPause() completed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (CameraViewListener listener : listeners) {
|
|
||||||
listener.onCameraStop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStarted() {
|
|
||||||
return state != State.PAUSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
||||||
final int width = r - l;
|
|
||||||
final int height = b - t;
|
|
||||||
final int previewWidth;
|
|
||||||
final int previewHeight;
|
|
||||||
|
|
||||||
if (camera.isPresent() && previewSize != null) {
|
|
||||||
if (displayOrientation == 90 || displayOrientation == 270) {
|
|
||||||
previewWidth = previewSize.height;
|
|
||||||
previewHeight = previewSize.width;
|
|
||||||
} else {
|
|
||||||
previewWidth = previewSize.width;
|
|
||||||
previewHeight = previewSize.height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
previewWidth = width;
|
|
||||||
previewHeight = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previewHeight == 0 || previewWidth == 0) {
|
|
||||||
Log.w(TAG, "skipping layout due to zero-width/height preview size");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width * previewHeight > height * previewWidth) {
|
|
||||||
final int scaledChildHeight = previewHeight * width / previewWidth;
|
|
||||||
surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
|
|
||||||
} else {
|
|
||||||
final int scaledChildWidth = previewWidth * height / previewHeight;
|
|
||||||
surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
||||||
Log.i(TAG, "onSizeChanged(" + oldw + "x" + oldh + " -> " + w + "x" + h + ")");
|
|
||||||
super.onSizeChanged(w, h, oldw, oldh);
|
|
||||||
if (camera.isPresent()) startPreview(camera.get().getParameters());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener(@NonNull CameraViewListener listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreviewCallback(final @NonNull QrCameraView.PreviewCallback previewCallback) {
|
|
||||||
enqueueTask(new PostInitializationTask<Void>() {
|
|
||||||
@Override
|
|
||||||
protected void onPostMain(Void avoid) {
|
|
||||||
if (camera.isPresent()) {
|
|
||||||
camera.get().setPreviewCallback(new Camera.PreviewCallback() {
|
|
||||||
@Override
|
|
||||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
|
||||||
if (!CameraView.this.camera.isPresent()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int rotation = getCameraPictureOrientation();
|
|
||||||
final Size previewSize = camera.getParameters().getPreviewSize();
|
|
||||||
if (data != null) {
|
|
||||||
previewCallback.onPreviewFrame(new QrCameraView.PreviewFrame(data, previewSize.width, previewSize.height, rotation));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMultiCamera() {
|
|
||||||
return Camera.getNumberOfCameras() > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRearCamera() {
|
|
||||||
return cameraId == CameraInfo.CAMERA_FACING_BACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void flipCamera() {
|
|
||||||
if (Camera.getNumberOfCameras() > 1) {
|
|
||||||
cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK
|
|
||||||
? CameraInfo.CAMERA_FACING_FRONT
|
|
||||||
: CameraInfo.CAMERA_FACING_BACK;
|
|
||||||
onPause();
|
|
||||||
onResume();
|
|
||||||
TextSecurePreferences.setDirectCaptureCameraId(getContext(), cameraId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(14)
|
|
||||||
private void onCameraReady(final @NonNull Camera camera) {
|
|
||||||
final Parameters parameters = camera.getParameters();
|
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= 14) {
|
|
||||||
parameters.setRecordingHint(true);
|
|
||||||
final List<String> focusModes = parameters.getSupportedFocusModes();
|
|
||||||
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
|
||||||
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
|
||||||
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
|
|
||||||
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displayOrientation = CameraUtils.getCameraDisplayOrientation(getActivity(), getCameraInfo());
|
|
||||||
camera.setDisplayOrientation(displayOrientation);
|
|
||||||
camera.setParameters(parameters);
|
|
||||||
enqueueTask(new PostInitializationTask<Void>() {
|
|
||||||
@Override
|
|
||||||
protected Void onRunBackground() {
|
|
||||||
try {
|
|
||||||
camera.setPreviewDisplay(surface.getHolder());
|
|
||||||
startPreview(parameters);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "couldn't set preview display", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPreview(final @NonNull Parameters parameters) {
|
|
||||||
if (this.camera.isPresent()) {
|
|
||||||
try {
|
|
||||||
final Camera camera = this.camera.get();
|
|
||||||
final Size preferredPreviewSize = getPreferredPreviewSize(parameters);
|
|
||||||
|
|
||||||
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
|
|
||||||
Log.i(TAG, "starting preview with size " + preferredPreviewSize.width + "x" + preferredPreviewSize.height);
|
|
||||||
if (state == State.ACTIVE) stopPreview();
|
|
||||||
previewSize = preferredPreviewSize;
|
|
||||||
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
|
|
||||||
camera.setParameters(parameters);
|
|
||||||
} else {
|
|
||||||
previewSize = parameters.getPreviewSize();
|
|
||||||
}
|
|
||||||
long previewStartMillis = System.currentTimeMillis();
|
|
||||||
camera.startPreview();
|
|
||||||
Log.i(TAG, "camera.startPreview() -> " + (System.currentTimeMillis() - previewStartMillis) + "ms");
|
|
||||||
state = State.ACTIVE;
|
|
||||||
ThreadUtil.runOnMain(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
requestLayout();
|
|
||||||
for (CameraViewListener listener : listeners) {
|
|
||||||
listener.onCameraStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopPreview() {
|
|
||||||
if (camera.isPresent()) {
|
|
||||||
try {
|
|
||||||
camera.get().stopPreview();
|
|
||||||
state = State.RESUMED;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Size getPreferredPreviewSize(@NonNull Parameters parameters) {
|
|
||||||
return CameraUtils.getPreferredPreviewSize(displayOrientation,
|
|
||||||
getMeasuredWidth(),
|
|
||||||
getMeasuredHeight(),
|
|
||||||
parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getCameraPictureOrientation() {
|
|
||||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
|
||||||
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
|
|
||||||
.getDefaultDisplay()
|
|
||||||
.getOrientation());
|
|
||||||
} else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) {
|
|
||||||
outputOrientation = (360 - displayOrientation) % 360;
|
|
||||||
} else {
|
|
||||||
outputOrientation = displayOrientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputOrientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/signalapp/Signal-Android/issues/4715
|
|
||||||
private boolean isTroublemaker() {
|
|
||||||
return getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT &&
|
|
||||||
"JWR66Y".equals(Build.DISPLAY) &&
|
|
||||||
"yakju".equals(Build.PRODUCT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NonNull CameraInfo getCameraInfo() {
|
|
||||||
final CameraInfo info = new Camera.CameraInfo();
|
|
||||||
Camera.getCameraInfo(cameraId, info);
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX this sucks
|
|
||||||
private Activity getActivity() {
|
|
||||||
return (Activity)getContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCameraPictureRotation(int orientation) {
|
|
||||||
final CameraInfo info = getCameraInfo();
|
|
||||||
final int rotation;
|
|
||||||
|
|
||||||
orientation = (orientation + 45) / 90 * 90;
|
|
||||||
|
|
||||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
|
||||||
rotation = (info.orientation - orientation + 360) % 360;
|
|
||||||
} else {
|
|
||||||
rotation = (info.orientation + orientation) % 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnOrientationChange extends OrientationEventListener {
|
|
||||||
public OnOrientationChange(Context context) {
|
|
||||||
super(context);
|
|
||||||
disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOrientationChanged(int orientation) {
|
|
||||||
if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) {
|
|
||||||
int newOutputOrientation = getCameraPictureRotation(orientation);
|
|
||||||
|
|
||||||
if (newOutputOrientation != outputOrientation) {
|
|
||||||
outputOrientation = newOutputOrientation;
|
|
||||||
|
|
||||||
Camera.Parameters params = camera.get().getParameters();
|
|
||||||
|
|
||||||
params.setRotation(outputOrientation);
|
|
||||||
|
|
||||||
try {
|
|
||||||
camera.get().setParameters(params);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
Log.e(TAG, "Exception updating camera parameters in orientation change", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void takePicture(final Rect previewRect) {
|
|
||||||
if (!camera.isPresent() || camera.get().getParameters() == null) {
|
|
||||||
Log.w(TAG, "camera not in capture-ready state");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
camera.get().setOneShotPreviewCallback(new Camera.PreviewCallback() {
|
|
||||||
@Override
|
|
||||||
public void onPreviewFrame(byte[] data, final Camera camera) {
|
|
||||||
final int rotation = getCameraPictureOrientation();
|
|
||||||
final Size previewSize = camera.getParameters().getPreviewSize();
|
|
||||||
final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation);
|
|
||||||
|
|
||||||
Log.i(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
|
|
||||||
Log.i(TAG, "data bytes: " + data.length);
|
|
||||||
Log.i(TAG, "previewFormat: " + camera.getParameters().getPreviewFormat());
|
|
||||||
Log.i(TAG, "croppingRect: " + croppingRect.toString());
|
|
||||||
Log.i(TAG, "rotation: " + rotation);
|
|
||||||
new CaptureTask(previewSize, rotation, croppingRect).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) {
|
|
||||||
final int previewWidth = cameraPreviewSize.width;
|
|
||||||
final int previewHeight = cameraPreviewSize.height;
|
|
||||||
|
|
||||||
if (rotation % 180 > 0) rotateRect(visibleRect);
|
|
||||||
|
|
||||||
float scale = (float) previewWidth / visibleRect.width();
|
|
||||||
if (visibleRect.height() * scale > previewHeight) {
|
|
||||||
scale = (float) previewHeight / visibleRect.height();
|
|
||||||
}
|
|
||||||
final float newWidth = visibleRect.width() * scale;
|
|
||||||
final float newHeight = visibleRect.height() * scale;
|
|
||||||
final float centerX = (VERSION.SDK_INT < 14 || isTroublemaker()) ? previewWidth - newWidth / 2 : previewWidth / 2;
|
|
||||||
final float centerY = previewHeight / 2;
|
|
||||||
|
|
||||||
visibleRect.set((int) (centerX - newWidth / 2),
|
|
||||||
(int) (centerY - newHeight / 2),
|
|
||||||
(int) (centerX + newWidth / 2),
|
|
||||||
(int) (centerY + newHeight / 2));
|
|
||||||
|
|
||||||
if (rotation % 180 > 0) rotateRect(visibleRect);
|
|
||||||
return visibleRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
|
||||||
private void rotateRect(Rect rect) {
|
|
||||||
rect.set(rect.top, rect.left, rect.bottom, rect.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enqueueTask(SerialAsyncTask job) {
|
|
||||||
AsyncTask.SERIAL_EXECUTOR.execute(job);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static abstract class SerialAsyncTask<Result> implements Runnable {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void run() {
|
|
||||||
if (!onWait()) {
|
|
||||||
Log.w(TAG, "skipping task, preconditions not met in onWait()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadUtil.runOnMainSync(this::onPreMain);
|
|
||||||
final Result result = onRunBackground();
|
|
||||||
ThreadUtil.runOnMainSync(() -> onPostMain(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onWait() { return true; }
|
|
||||||
protected void onPreMain() {}
|
|
||||||
protected Result onRunBackground() { return null; }
|
|
||||||
protected void onPostMain(Result result) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class PostInitializationTask<Result> extends SerialAsyncTask<Result> {
|
|
||||||
@Override protected boolean onWait() {
|
|
||||||
synchronized (CameraView.this) {
|
|
||||||
if (!camera.isPresent()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) {
|
|
||||||
Log.i(TAG, String.format("waiting. surface ready? %s", surface.isReady()));
|
|
||||||
Util.wait(CameraView.this, 0);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CaptureTask extends AsyncTask<byte[], Void, byte[]> {
|
|
||||||
private final Size previewSize;
|
|
||||||
private final int rotation;
|
|
||||||
private final Rect croppingRect;
|
|
||||||
|
|
||||||
public CaptureTask(Size previewSize, int rotation, Rect croppingRect) {
|
|
||||||
this.previewSize = previewSize;
|
|
||||||
this.rotation = rotation;
|
|
||||||
this.croppingRect = croppingRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] doInBackground(byte[]... params) {
|
|
||||||
final byte[] data = params[0];
|
|
||||||
try {
|
|
||||||
return BitmapUtil.createFromNV21(data,
|
|
||||||
previewSize.width,
|
|
||||||
previewSize.height,
|
|
||||||
rotation,
|
|
||||||
croppingRect,
|
|
||||||
cameraId == CameraInfo.CAMERA_FACING_FRONT);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(byte[] imageBytes) {
|
|
||||||
if (imageBytes != null) {
|
|
||||||
for (CameraViewListener listener : listeners) {
|
|
||||||
listener.onImageCapture(imageBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PreconditionsNotMetException extends Exception {}
|
|
||||||
|
|
||||||
public interface CameraViewListener {
|
|
||||||
void onImageCapture(@NonNull final byte[] imageBytes);
|
|
||||||
void onCameraFail();
|
|
||||||
void onCameraStart();
|
|
||||||
void onCameraStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum State {
|
|
||||||
PAUSED, RESUMED, ACTIVE
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,22 +11,25 @@ import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
|
|
||||||
import org.signal.core.util.ThreadUtil;
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.qr.kitkat.ScanningThread;
|
import org.signal.qr.QrScannerView;
|
||||||
import org.thoughtcrime.securesms.LoggingFragment;
|
import org.thoughtcrime.securesms.LoggingFragment;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
|
||||||
import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress;
|
import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress;
|
||||||
|
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
public final class PaymentsTransferQrScanFragment extends LoggingFragment {
|
public final class PaymentsTransferQrScanFragment extends LoggingFragment {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(PaymentsTransferQrScanFragment.class);
|
private static final String TAG = Log.tag(PaymentsTransferQrScanFragment.class);
|
||||||
|
|
||||||
|
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
|
||||||
|
|
||||||
private LinearLayout overlay;
|
private LinearLayout overlay;
|
||||||
private CameraView scannerView;
|
private QrScannerView scannerView;
|
||||||
private ScanningThread scanningThread;
|
|
||||||
private PaymentsTransferViewModel viewModel;
|
private PaymentsTransferViewModel viewModel;
|
||||||
|
|
||||||
public PaymentsTransferQrScanFragment() {
|
public PaymentsTransferQrScanFragment() {
|
||||||
|
@ -48,45 +51,35 @@ public final class PaymentsTransferQrScanFragment extends LoggingFragment {
|
||||||
|
|
||||||
Toolbar toolbar = view.findViewById(R.id.payments_transfer_scan_qr);
|
Toolbar toolbar = view.findViewById(R.id.payments_transfer_scan_qr);
|
||||||
toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(v).popBackStack());
|
toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(v).popBackStack());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
scannerView.start(getViewLifecycleOwner());
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
||||||
scanningThread = new ScanningThread();
|
|
||||||
scanningThread.setScanListener(data -> ThreadUtil.runOnMain(() -> {
|
Disposable qrDisposable = scannerView
|
||||||
|
.getQrData()
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(data -> {
|
||||||
try {
|
try {
|
||||||
viewModel.postQrData(MobileCoinPublicAddress.fromQr(data).getPaymentAddressBase58());
|
viewModel.postQrData(MobileCoinPublicAddress.fromQr(data).getPaymentAddressBase58());
|
||||||
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_paymentsScanQr_pop);
|
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_paymentsScanQr_pop);
|
||||||
} catch (MobileCoinPublicAddress.AddressException e) {
|
} catch (MobileCoinPublicAddress.AddressException e) {
|
||||||
Log.e(TAG, "Not a valid address");
|
Log.e(TAG, "Not a valid address");
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
scannerView.onResume();
|
|
||||||
scannerView.setPreviewCallback(scanningThread);
|
|
||||||
scanningThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
lifecycleDisposable.add(qrDisposable);
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
scannerView.onPause();
|
|
||||||
scanningThread.stopScanning();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
|
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
|
||||||
super.onConfigurationChanged(newConfiguration);
|
super.onConfigurationChanged(newConfiguration);
|
||||||
|
|
||||||
scannerView.onPause();
|
|
||||||
|
|
||||||
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
overlay.setOrientation(LinearLayout.HORIZONTAL);
|
overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
} else {
|
} else {
|
||||||
overlay.setOrientation(LinearLayout.VERTICAL);
|
overlay.setOrientation(LinearLayout.VERTICAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
scannerView.onResume();
|
|
||||||
scannerView.setPreviewCallback(scanningThread);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,6 @@ public final class FeatureFlags {
|
||||||
private static final String STORIES_AUTO_DOWNLOAD_MAXIMUM = "android.stories.autoDownloadMaximum";
|
private static final String STORIES_AUTO_DOWNLOAD_MAXIMUM = "android.stories.autoDownloadMaximum";
|
||||||
private static final String GIFT_BADGE_RECEIVE_SUPPORT = "android.giftBadges.receiving";
|
private static final String GIFT_BADGE_RECEIVE_SUPPORT = "android.giftBadges.receiving";
|
||||||
private static final String GIFT_BADGE_SEND_SUPPORT = "android.giftBadges.sending";
|
private static final String GIFT_BADGE_SEND_SUPPORT = "android.giftBadges.sending";
|
||||||
private static final String USE_QR_LEGACY_SCAN = "android.qr.legacy_scan";
|
|
||||||
private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList";
|
private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList";
|
||||||
private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList";
|
private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList";
|
||||||
|
|
||||||
|
@ -150,7 +149,6 @@ public final class FeatureFlags {
|
||||||
STORIES_AUTO_DOWNLOAD_MAXIMUM,
|
STORIES_AUTO_DOWNLOAD_MAXIMUM,
|
||||||
GIFT_BADGE_RECEIVE_SUPPORT,
|
GIFT_BADGE_RECEIVE_SUPPORT,
|
||||||
GIFT_BADGE_SEND_SUPPORT,
|
GIFT_BADGE_SEND_SUPPORT,
|
||||||
USE_QR_LEGACY_SCAN,
|
|
||||||
TELECOM_MANUFACTURER_ALLOWLIST,
|
TELECOM_MANUFACTURER_ALLOWLIST,
|
||||||
TELECOM_MODEL_BLOCKLIST
|
TELECOM_MODEL_BLOCKLIST
|
||||||
);
|
);
|
||||||
|
@ -212,7 +210,6 @@ public final class FeatureFlags {
|
||||||
USE_AEC3,
|
USE_AEC3,
|
||||||
PAYMENTS_COUNTRY_BLOCKLIST,
|
PAYMENTS_COUNTRY_BLOCKLIST,
|
||||||
USE_FCM_FOREGROUND_SERVICE,
|
USE_FCM_FOREGROUND_SERVICE,
|
||||||
USE_QR_LEGACY_SCAN,
|
|
||||||
TELECOM_MANUFACTURER_ALLOWLIST,
|
TELECOM_MANUFACTURER_ALLOWLIST,
|
||||||
TELECOM_MODEL_BLOCKLIST
|
TELECOM_MODEL_BLOCKLIST
|
||||||
);
|
);
|
||||||
|
@ -530,10 +527,6 @@ public final class FeatureFlags {
|
||||||
return giftBadgeReceiveSupport() && getBoolean(GIFT_BADGE_SEND_SUPPORT, Environment.IS_STAGING);
|
return giftBadgeReceiveSupport() && getBoolean(GIFT_BADGE_SEND_SUPPORT, Environment.IS_STAGING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean useQrLegacyScan() {
|
|
||||||
return getBoolean(USE_QR_LEGACY_SCAN, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Only for rendering debug info. */
|
/** Only for rendering debug info. */
|
||||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||||
return new TreeMap<>(REMOTE_VALUES);
|
return new TreeMap<>(REMOTE_VALUES);
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.thoughtcrime.securesms.verify
|
package org.thoughtcrime.securesms.verify
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -9,28 +7,24 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.view.OneShotPreDrawListener
|
import androidx.core.view.OneShotPreDrawListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import org.signal.qr.QrScannerView
|
||||||
import org.signal.qr.kitkat.ScanListener
|
import org.signal.qr.kitkat.ScanListener
|
||||||
import org.signal.qr.kitkat.ScanningThread
|
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.ShapeScrim
|
import org.thoughtcrime.securesms.components.ShapeScrim
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
import org.thoughtcrime.securesms.util.fragments.findListener
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QR Scanner for identity verification
|
* QR Scanner for identity verification
|
||||||
*/
|
*/
|
||||||
class VerifyScanFragment : Fragment() {
|
class VerifyScanFragment : Fragment() {
|
||||||
private lateinit var cameraView: CameraView
|
private val lifecycleDisposable = LifecycleDisposable()
|
||||||
|
|
||||||
|
private lateinit var cameraView: QrScannerView
|
||||||
private lateinit var cameraScrim: ShapeScrim
|
private lateinit var cameraScrim: ShapeScrim
|
||||||
private lateinit var cameraMarks: ImageView
|
private lateinit var cameraMarks: ImageView
|
||||||
private lateinit var scanningThread: ScanningThread
|
|
||||||
private lateinit var scanListener: ScanListener
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
super.onAttach(context)
|
|
||||||
scanListener = requireListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
|
||||||
return ViewUtil.inflate(inflater, viewGroup!!, R.layout.verify_scan_fragment)
|
return ViewUtil.inflate(inflater, viewGroup!!, R.layout.verify_scan_fragment)
|
||||||
|
@ -45,28 +39,17 @@ class VerifyScanFragment : Fragment() {
|
||||||
val height = cameraScrim.scrimHeight
|
val height = cameraScrim.scrimHeight
|
||||||
ViewUtil.updateLayoutParams(cameraMarks, width, height)
|
ViewUtil.updateLayoutParams(cameraMarks, width, height)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
cameraView.start(viewLifecycleOwner)
|
||||||
super.onResume()
|
|
||||||
scanningThread = ScanningThread()
|
|
||||||
scanningThread.setScanListener(scanListener)
|
|
||||||
scanningThread.setCharacterSet("ISO-8859-1")
|
|
||||||
cameraView.onResume()
|
|
||||||
cameraView.setPreviewCallback(scanningThread)
|
|
||||||
scanningThread.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||||
super.onPause()
|
|
||||||
cameraView.onPause()
|
|
||||||
scanningThread.stopScanning()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfiguration: Configuration) {
|
lifecycleDisposable += cameraView
|
||||||
super.onConfigurationChanged(newConfiguration)
|
.qrData
|
||||||
cameraView.onPause()
|
.distinctUntilChanged()
|
||||||
cameraView.onResume()
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
cameraView.setPreviewCallback(scanningThread)
|
.subscribe { qrData: String ->
|
||||||
|
findListener<ScanListener>()?.onQrDataFound(qrData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.camera.CameraView
|
|
||||||
android:id="@+id/scanner"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:camera="0" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/overlay"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:weightSum="2">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.ShapeScrim
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="?android:windowBackground"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Position the QR code within the frame"
|
|
||||||
android:textColor="?android:textColorSecondary"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -23,11 +23,10 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/payments_transfer_scan_qr">
|
app:layout_constraintTop_toBottomOf="@+id/payments_transfer_scan_qr">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.camera.CameraView
|
<org.signal.qr.QrScannerView
|
||||||
android:id="@+id/scanner"
|
android:id="@+id/scanner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent" />
|
||||||
app:camera="0" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/overlay"
|
android:id="@+id/overlay"
|
||||||
|
|
|
@ -15,11 +15,10 @@
|
||||||
app:layout_constraintEnd_toStartOf="parent"
|
app:layout_constraintEnd_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.camera.CameraView
|
<org.signal.qr.QrScannerView
|
||||||
android:id="@+id/scanner"
|
android:id="@+id/scanner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:camera="0"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="parent"
|
app:layout_constraintEnd_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
@ -40,6 +40,7 @@ class QrScannerView @JvmOverloads constructor(
|
||||||
this.scannerView = (scannerView as ScannerView)
|
this.scannerView = (scannerView as ScannerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
fun start(lifecycleOwner: LifecycleOwner, forceLegacy: Boolean = false) {
|
fun start(lifecycleOwner: LifecycleOwner, forceLegacy: Boolean = false) {
|
||||||
if (scannerView != null) {
|
if (scannerView != null) {
|
||||||
Log.w(TAG, "Attempt to start scanning that has already started")
|
Log.w(TAG, "Attempt to start scanning that has already started")
|
||||||
|
|
Ładowanie…
Reference in New Issue