kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix QR processing resolution and allow front camera use for device linking.
rodzic
3d14c05114
commit
ea9bf0ccd5
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -67,7 +67,7 @@ class QrProcessor {
|
|||
|
||||
companion object {
|
||||
private val TAG = Log.tag(QrProcessor::class.java)
|
||||
|
||||
/** For debugging only */
|
||||
var listener: ((LuminanceSource) -> Unit)? = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ import androidx.lifecycle.LifecycleOwner
|
|||
*/
|
||||
interface ScannerView {
|
||||
fun start(lifecycleOwner: LifecycleOwner)
|
||||
fun toggleCamera()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Ładowanie…
Reference in New Issue