Fix QR processing resolution and allow front camera use for device linking.

main
Cody Henthorne 2022-10-13 09:57:44 -04:00 zatwierdzone przez Alex Hart
rodzic 3d14c05114
commit ea9bf0ccd5
11 zmienionych plików z 215 dodań i 52 usunięć

Wyświetl plik

@ -10,6 +10,8 @@ import android.os.Bundle;
import android.os.Vibrator;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
@ -54,6 +56,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
private DeviceAddFragment deviceAddFragment;
private DeviceListFragment deviceListFragment;
private DeviceLinkFragment deviceLinkFragment;
private MenuItem cameraSwitchItem = null;
@Override
public void onPreCreate() {
@ -102,6 +105,18 @@ public class DeviceActivity extends PassphraseRequiredActivity
return false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.device_add, menu);
cameraSwitchItem = menu.findItem(R.id.device_add_camera_switch);
cameraSwitchItem.setVisible(false);
return super.onCreateOptionsMenu(menu);
}
public MenuItem getCameraSwitchItem() {
return cameraSwitchItem;
}
@Override
public void onClick(View v) {
Permissions.with(this)

Wyświetl plik

@ -2,24 +2,23 @@ package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import org.signal.qr.QrScannerView;
import org.signal.qr.kitkat.ScanListener;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXModelBlocklist;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.ViewUtil;
@ -32,12 +31,13 @@ public class DeviceAddFragment extends LoggingFragment {
private ImageView devicesImage;
private ScanListener scanListener;
private QrScannerView scannerView;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
ViewGroup container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
QrScannerView scannerView = container.findViewById(R.id.scanner);
this.scannerView = container.findViewById(R.id.scanner);
this.devicesImage = container.findViewById(R.id.devices);
ViewCompat.setTransitionName(devicesImage, "devices");
@ -77,6 +77,19 @@ public class DeviceAddFragment extends LoggingFragment {
return container;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
MenuItem switchCamera = ((DeviceActivity) requireActivity()).getCameraSwitchItem();
if (switchCamera != null) {
switchCamera.setVisible(true);
switchCamera.setOnMenuItemClickListener(v -> {
scannerView.toggleCamera();
return true;
});
}
}
public ImageView getDevicesImage() {
return devicesImage;
}

Wyświetl plik

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/device_add_camera_switch"
android:icon="@drawable/ic_switch_camera_24"
android:title="@string/CameraXFragment_change_camera_description"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always"/>
</menu>

Wyświetl plik

@ -4,6 +4,8 @@ import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
@ -12,16 +14,63 @@ import com.google.zxing.PlanarYUVLuminanceSource
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.ThreadUtil
import org.signal.core.util.logging.AndroidLogger
import org.signal.core.util.logging.Log
import org.signal.qr.ImageProxyLuminanceSource
import org.signal.qr.QrProcessor
import org.signal.qr.QrScannerView
class QrMainActivity : AppCompatActivity() {
private lateinit var text: EditText
@SuppressLint("NewApi", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
Log.initialize(AndroidLogger(), object : Log.Logger() {
override fun v(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) {
printlnFormatted('v', tag, message, t)
}
override fun d(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) {
printlnFormatted('d', tag, message, t)
}
override fun i(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) {
printlnFormatted('i', tag, message, t)
}
override fun w(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) {
printlnFormatted('w', tag, message, t)
}
override fun e(tag: String, message: String?, t: Throwable?, keepLonger: Boolean) {
printlnFormatted('e', tag, message, t)
}
override fun flush() {}
private fun printlnFormatted(level: Char, tag: String, message: String?, t: Throwable?) {
ThreadUtil.runOnMain {
val allText = text.text.toString() + "\n" + format(level, tag, message, t)
text.setText(allText)
}
}
private fun format(level: Char, tag: String, message: String?, t: Throwable?): String {
return if (t != null) {
String.format("%c[%s] %s %s:%s", level, tag, message, t.javaClass.simpleName, t.message)
} else {
String.format("%c[%s] %s", level, tag, message)
}
}
})
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
text = findViewById(R.id.log)
if (Build.VERSION.SDK_INT >= 23) {
requestPermissions(arrayOf(android.Manifest.permission.CAMERA), 1)
}
@ -56,5 +105,9 @@ class QrMainActivity : AppCompatActivity() {
}
}
}
findViewById<View>(R.id.camera_switch).setOnClickListener {
scanner.toggleCamera()
}
}
}

Wyświetl plik

@ -1,35 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
<Button
android:id="@+id/camera_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical">
android:text="Change Camera" />
<TextView
android:id="@+id/text_qr_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text_qr_data"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text_size"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/teal_200">
<org.signal.qr.QrScannerView
android:id="@+id/scanner"
android:layout_width="240dp"
android:layout_height="240dp" />
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/teal_700">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/scanner_source"
android:layout_width="240dp"
android:layout_height="240dp" />
android:layout_height="240dp"
android:scaleType="fitCenter"
android:rotation="90"
tools:src="@tools:sample/backgrounds/scenic" />
</FrameLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/log"
android:layout_width="match_parent"
android:layout_height="100dp" />
</FrameLayout>
</LinearLayout>

Wyświetl plik

@ -67,7 +67,7 @@ class QrProcessor {
companion object {
private val TAG = Log.tag(QrProcessor::class.java)
/** For debugging only */
var listener: ((LuminanceSource) -> Unit)? = null
}
}

Wyświetl plik

@ -57,6 +57,11 @@ class QrScannerView @JvmOverloads constructor(
})
}
fun toggleCamera() {
Log.d(TAG, "Toggling camera")
scannerView?.toggleCamera()
}
companion object {
private val TAG = Log.tag(QrScannerView::class.java)
}

Wyświetl plik

@ -7,4 +7,5 @@ import androidx.lifecycle.LifecycleOwner
*/
interface ScannerView {
fun start(lifecycleOwner: LifecycleOwner)
fun toggleCamera()
}

Wyświetl plik

@ -18,8 +18,27 @@ internal class ScannerView19 constructor(
private val scanListener: ScanListener
) : FrameLayout(context), ScannerView {
private var lifecycleOwner: LifecycleOwner? = null
private var scanningThread: ScanningThread? = null
private val cameraView: QrCameraView
private lateinit var cameraView: QrCameraView
private val lifecycleObserver = object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
val scanningThread = ScanningThread()
scanningThread.setScanListener(scanListener)
cameraView.onResume()
cameraView.setPreviewCallback(scanningThread)
scanningThread.start()
this@ScannerView19.scanningThread = scanningThread
}
override fun onPause(owner: LifecycleOwner) {
cameraView.onPause()
scanningThread?.stopScanning()
scanningThread = null
}
}
init {
cameraView = QrCameraView(context)
@ -28,22 +47,16 @@ internal class ScannerView19 constructor(
}
override fun start(lifecycleOwner: LifecycleOwner) {
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
val scanningThread = ScanningThread()
scanningThread.setScanListener(scanListener)
cameraView.onResume()
cameraView.setPreviewCallback(scanningThread)
scanningThread.start()
this.lifecycleOwner?.lifecycle?.removeObserver(lifecycleObserver)
this.lifecycleOwner = lifecycleOwner
lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
}
this@ScannerView19.scanningThread = scanningThread
}
override fun onPause(owner: LifecycleOwner) {
cameraView.onPause()
scanningThread?.stopScanning()
scanningThread = null
}
})
override fun toggleCamera() {
cameraView.toggleCamera()
lifecycleOwner?.let {
lifecycleObserver.onPause(it)
lifecycleObserver.onResume(it)
}
}
}

Wyświetl plik

@ -2,9 +2,9 @@ package org.signal.qr
import android.annotation.SuppressLint
import android.content.Context
import android.util.Size
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.camera.core.AspectRatio
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
@ -28,19 +28,42 @@ internal class ScannerView21 constructor(
private val listener: ScanListener
) : FrameLayout(context), ScannerView {
private var lifecyleOwner: LifecycleOwner? = null
private val analyzerExecutor = Executors.newSingleThreadExecutor()
private var cameraProvider: ProcessCameraProvider? = null
private var cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
private var camera: Camera? = null
private var previewView: PreviewView
private val qrProcessor = QrProcessor()
private val lifecycleObserver: DefaultLifecycleObserver = object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
cameraProvider = null
camera = null
analyzerExecutor.shutdown()
}
}
init {
previewView = PreviewView(context)
previewView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(previewView)
}
override fun toggleCamera() {
cameraSelector = if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
CameraSelector.DEFAULT_FRONT_CAMERA
} else {
CameraSelector.DEFAULT_BACK_CAMERA
}
lifecyleOwner?.let { start(it) }
}
override fun start(lifecycleOwner: LifecycleOwner) {
this.lifecyleOwner?.lifecycle?.removeObserver(lifecycleObserver)
this.lifecyleOwner = lifecycleOwner
previewView.post {
Log.i(TAG, "Starting")
ProcessCameraProvider.getInstance(context).apply {
@ -54,13 +77,7 @@ internal class ScannerView21 constructor(
}
}
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
cameraProvider = null
camera = null
analyzerExecutor.shutdown()
}
})
lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
}
private fun onCameraProvider(lifecycle: LifecycleOwner, cameraProvider: ProcessCameraProvider?) {
@ -71,10 +88,14 @@ internal class ScannerView21 constructor(
Log.i(TAG, "Initializing use cases")
val preview = Preview.Builder().build()
val resolution = Size(480, 640)
val preview = Preview.Builder()
.setTargetResolution(resolution)
.build()
val imageAnalysis = ImageAnalysis.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetResolution(resolution)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
@ -88,10 +109,13 @@ internal class ScannerView21 constructor(
}
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(lifecycle, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
camera = cameraProvider.bindToLifecycle(lifecycle, cameraSelector, preview, imageAnalysis)
preview.setSurfaceProvider(previewView.surfaceProvider)
Log.d(TAG, "Preview: ${preview.resolutionInfo}")
Log.d(TAG, "Analysis: ${imageAnalysis.resolutionInfo}")
this.cameraProvider = cameraProvider
}

Wyświetl plik

@ -47,7 +47,7 @@ public class QrCameraView extends ViewGroup {
private final OnOrientationChange onOrientationChange;
private volatile Optional<Camera> camera = Optional.empty();
private final int cameraId = CameraInfo.CAMERA_FACING_BACK;
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
private volatile int displayOrientation = -1;
private @NonNull State state = State.PAUSED;
@ -161,6 +161,14 @@ public class QrCameraView extends ViewGroup {
return state != State.PAUSED;
}
public void toggleCamera() {
if (cameraId == CameraInfo.CAMERA_FACING_BACK) {
cameraId = CameraInfo.CAMERA_FACING_FRONT;
} else {
cameraId = CameraInfo.CAMERA_FACING_BACK;
}
}
@SuppressWarnings("SuspiciousNameCombination")
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {