From 18eac51576f191df01c1c35f9a35ff0a18dfe8c7 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 3 Aug 2022 11:32:53 -0400 Subject: [PATCH] Migrate all QR scanning to new scanner. --- .../securesms/DeviceAddFragment.java | 2 +- .../components/camera/CameraSurfaceView.java | 33 - .../components/camera/CameraUtils.java | 110 ---- .../components/camera/CameraView.java | 575 ------------------ .../PaymentsTransferQrScanFragment.java | 57 +- .../securesms/util/FeatureFlags.java | 7 - .../securesms/verify/VerifyScanFragment.kt | 49 +- .../old_device_transfer_setup_fragment.xml | 56 -- .../payments_transfer_qr_scan_fragment.xml | 5 +- .../main/res/layout/verify_scan_fragment.xml | 3 +- .../main/java/org/signal/qr/QrScannerView.kt | 1 + 11 files changed, 46 insertions(+), 852 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java delete mode 100644 app/src/main/res/layout/old_device_transfer_setup_fragment.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeviceAddFragment.java b/app/src/main/java/org/thoughtcrime/securesms/DeviceAddFragment.java index 50e78d73f4..4ddd386bd7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/DeviceAddFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/DeviceAddFragment.java @@ -57,7 +57,7 @@ public class DeviceAddFragment extends LoggingFragment { }); } - scannerView.start(getViewLifecycleOwner(), FeatureFlags.useQrLegacyScan()); + scannerView.start(getViewLifecycleOwner()); lifecycleDisposable.bindTo(getViewLifecycleOwner()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java deleted file mode 100644 index 7a991eae5c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraSurfaceView.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java deleted file mode 100644 index 7701cbbc79..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraUtils.java +++ /dev/null @@ -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 sizes = parameters.getSupportedPreviewSizes(); - List ideals = new LinkedList<>(); - List 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 { - @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); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java deleted file mode 100644 index c89e48d306..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java +++ /dev/null @@ -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 = 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 listeners = Collections.synchronizedList(new LinkedList()); - 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() { - @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() { - private Optional 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() { - @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 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() { - @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 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 extends SerialAsyncTask { - @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 { - 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 - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java index d959dc9079..8d226e1e01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/transfer/PaymentsTransferQrScanFragment.java @@ -11,22 +11,25 @@ import androidx.appcompat.widget.Toolbar; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.Navigation; -import org.signal.core.util.ThreadUtil; 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.R; -import org.thoughtcrime.securesms.components.camera.CameraView; import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress; +import org.thoughtcrime.securesms.util.LifecycleDisposable; 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 { private static final String TAG = Log.tag(PaymentsTransferQrScanFragment.class); + private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable(); + private LinearLayout overlay; - private CameraView scannerView; - private ScanningThread scanningThread; + private QrScannerView scannerView; private PaymentsTransferViewModel viewModel; public PaymentsTransferQrScanFragment() { @@ -48,45 +51,35 @@ public final class PaymentsTransferQrScanFragment extends LoggingFragment { Toolbar toolbar = view.findViewById(R.id.payments_transfer_scan_qr); toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(v).popBackStack()); - } - @Override - public void onResume() { - super.onResume(); - scanningThread = new ScanningThread(); - scanningThread.setScanListener(data -> ThreadUtil.runOnMain(() -> { - try { - viewModel.postQrData(MobileCoinPublicAddress.fromQr(data).getPaymentAddressBase58()); - SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_paymentsScanQr_pop); - } catch (MobileCoinPublicAddress.AddressException e) { - Log.e(TAG, "Not a valid address"); - } - })); - scannerView.onResume(); - scannerView.setPreviewCallback(scanningThread); - scanningThread.start(); - } + scannerView.start(getViewLifecycleOwner()); - @Override - public void onPause() { - super.onPause(); - scannerView.onPause(); - scanningThread.stopScanning(); + lifecycleDisposable.bindTo(getViewLifecycleOwner()); + + Disposable qrDisposable = scannerView + .getQrData() + .distinctUntilChanged() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(data -> { + try { + viewModel.postQrData(MobileCoinPublicAddress.fromQr(data).getPaymentAddressBase58()); + SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_paymentsScanQr_pop); + } catch (MobileCoinPublicAddress.AddressException e) { + Log.e(TAG, "Not a valid address"); + } + }); + + lifecycleDisposable.add(qrDisposable); } @Override public void onConfigurationChanged(@NonNull Configuration newConfiguration) { super.onConfigurationChanged(newConfiguration); - scannerView.onPause(); - if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) { overlay.setOrientation(LinearLayout.HORIZONTAL); } else { overlay.setOrientation(LinearLayout.VERTICAL); } - - scannerView.onResume(); - scannerView.setPreviewCallback(scanningThread); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index d6c7a0f2ee..c96f4a99e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -97,7 +97,6 @@ public final class FeatureFlags { 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_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_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList"; @@ -150,7 +149,6 @@ public final class FeatureFlags { STORIES_AUTO_DOWNLOAD_MAXIMUM, GIFT_BADGE_RECEIVE_SUPPORT, GIFT_BADGE_SEND_SUPPORT, - USE_QR_LEGACY_SCAN, TELECOM_MANUFACTURER_ALLOWLIST, TELECOM_MODEL_BLOCKLIST ); @@ -212,7 +210,6 @@ public final class FeatureFlags { USE_AEC3, PAYMENTS_COUNTRY_BLOCKLIST, USE_FCM_FOREGROUND_SERVICE, - USE_QR_LEGACY_SCAN, TELECOM_MANUFACTURER_ALLOWLIST, TELECOM_MODEL_BLOCKLIST ); @@ -530,10 +527,6 @@ public final class FeatureFlags { 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. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES); diff --git a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt index 98ee2b3d3a..d13a46c84c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyScanFragment.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.verify -import android.content.Context -import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,28 +7,24 @@ import android.view.ViewGroup import android.widget.ImageView import androidx.core.view.OneShotPreDrawListener 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.ScanningThread import org.thoughtcrime.securesms.R 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.fragments.requireListener +import org.thoughtcrime.securesms.util.fragments.findListener /** * QR Scanner for identity verification */ 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 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? { return ViewUtil.inflate(inflater, viewGroup!!, R.layout.verify_scan_fragment) @@ -45,28 +39,17 @@ class VerifyScanFragment : Fragment() { val height = cameraScrim.scrimHeight ViewUtil.updateLayoutParams(cameraMarks, width, height) } - } - override fun onResume() { - super.onResume() - scanningThread = ScanningThread() - scanningThread.setScanListener(scanListener) - scanningThread.setCharacterSet("ISO-8859-1") - cameraView.onResume() - cameraView.setPreviewCallback(scanningThread) - scanningThread.start() - } + cameraView.start(viewLifecycleOwner) - override fun onPause() { - super.onPause() - cameraView.onPause() - scanningThread.stopScanning() - } + lifecycleDisposable.bindTo(viewLifecycleOwner) - override fun onConfigurationChanged(newConfiguration: Configuration) { - super.onConfigurationChanged(newConfiguration) - cameraView.onPause() - cameraView.onResume() - cameraView.setPreviewCallback(scanningThread) + lifecycleDisposable += cameraView + .qrData + .distinctUntilChanged() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { qrData: String -> + findListener()?.onQrDataFound(qrData) + } } } diff --git a/app/src/main/res/layout/old_device_transfer_setup_fragment.xml b/app/src/main/res/layout/old_device_transfer_setup_fragment.xml deleted file mode 100644 index 68ce959c26..0000000000 --- a/app/src/main/res/layout/old_device_transfer_setup_fragment.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/payments_transfer_qr_scan_fragment.xml b/app/src/main/res/layout/payments_transfer_qr_scan_fragment.xml index ed15ff7e9d..897782993a 100644 --- a/app/src/main/res/layout/payments_transfer_qr_scan_fragment.xml +++ b/app/src/main/res/layout/payments_transfer_qr_scan_fragment.xml @@ -23,11 +23,10 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/payments_transfer_scan_qr"> - + android:layout_height="match_parent" /> -