mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-06-22 00:50:55 +00:00
Merge pull request #461 from alexbakker/better-scanner
Replace barcodescanner with CameraX and ZXing
This commit is contained in:
commit
c252e77c5b
5 changed files with 183 additions and 141 deletions
|
@ -91,6 +91,7 @@ protobuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
def cameraxVersion = '1.0.0-beta04'
|
||||||
def glideVersion = '4.11.0'
|
def glideVersion = '4.11.0'
|
||||||
def guavaVersion = '29.0'
|
def guavaVersion = '29.0'
|
||||||
def junitVersion = '5.6.2'
|
def junitVersion = '5.6.2'
|
||||||
|
@ -102,8 +103,12 @@ dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation "androidx.biometric:biometric:1.0.1"
|
implementation "androidx.biometric:biometric:1.0.1"
|
||||||
|
implementation "androidx.camera:camera-camera2:$cameraxVersion"
|
||||||
|
implementation "androidx.camera:camera-lifecycle:$cameraxVersion"
|
||||||
|
implementation "androidx.camera:camera-view:1.0.0-alpha11"
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
|
@ -120,12 +125,12 @@ dependencies {
|
||||||
implementation "com.google.guava:guava:${guavaVersion}-android"
|
implementation "com.google.guava:guava:${guavaVersion}-android"
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
implementation 'com.google.protobuf:protobuf-javalite:3.12.1'
|
implementation 'com.google.protobuf:protobuf-javalite:3.12.1'
|
||||||
|
implementation 'com.google.zxing:core:3.4.0'
|
||||||
implementation "com.mikepenz:iconics-core:3.2.5"
|
implementation "com.mikepenz:iconics-core:3.2.5"
|
||||||
implementation 'com.mikepenz:material-design-iconic-typeface:2.2.0.5@aar'
|
implementation 'com.mikepenz:material-design-iconic-typeface:2.2.0.5@aar'
|
||||||
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
||||||
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
||||||
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
|
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
|
||||||
implementation 'net.lingala.zip4j:zip4j:2.6.0'
|
implementation 'net.lingala.zip4j:zip4j:2.6.0'
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.beemdevelopment.aegis.helpers;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.camera.core.ImageAnalysis;
|
||||||
|
import androidx.camera.core.ImageProxy;
|
||||||
|
|
||||||
|
import com.google.zxing.BinaryBitmap;
|
||||||
|
import com.google.zxing.ChecksumException;
|
||||||
|
import com.google.zxing.FormatException;
|
||||||
|
import com.google.zxing.NotFoundException;
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
import com.google.zxing.common.HybridBinarizer;
|
||||||
|
import com.google.zxing.qrcode.QRCodeReader;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static android.graphics.ImageFormat.YUV_420_888;
|
||||||
|
import static android.graphics.ImageFormat.YUV_422_888;
|
||||||
|
import static android.graphics.ImageFormat.YUV_444_888;
|
||||||
|
|
||||||
|
public class QrCodeAnalyzer implements ImageAnalysis.Analyzer {
|
||||||
|
private static final String TAG = QrCodeAnalyzer.class.getSimpleName();
|
||||||
|
|
||||||
|
private final QrCodeAnalyzer.Listener _listener;
|
||||||
|
|
||||||
|
public QrCodeAnalyzer(QrCodeAnalyzer.Listener listener) {
|
||||||
|
_listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void analyze(@NonNull ImageProxy image) {
|
||||||
|
int format = image.getFormat();
|
||||||
|
if (format != YUV_420_888 && format != YUV_422_888 && format != YUV_444_888) {
|
||||||
|
Log.e(TAG, String.format("Expected YUV format, got %d instead", format));
|
||||||
|
image.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buf = image.getPlanes()[0].getBuffer();
|
||||||
|
byte[] data = new byte[buf.remaining()];
|
||||||
|
buf.get(data);
|
||||||
|
buf.rewind();
|
||||||
|
|
||||||
|
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
|
||||||
|
data, image.getWidth(), image.getHeight(), 0, 0, image.getWidth(), image.getHeight(), false
|
||||||
|
);
|
||||||
|
|
||||||
|
QRCodeReader reader = new QRCodeReader();
|
||||||
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
try {
|
||||||
|
Result result = reader.decode(bitmap);
|
||||||
|
if (_listener != null) {
|
||||||
|
_listener.onQrCodeDetected(result);
|
||||||
|
}
|
||||||
|
} catch (ChecksumException | FormatException | NotFoundException ignored) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
image.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onQrCodeDetected(Result result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
package com.beemdevelopment.aegis.helpers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import me.dm7.barcodescanner.core.ViewFinderView;
|
|
||||||
|
|
||||||
public class SquareFinderView extends ViewFinderView {
|
|
||||||
|
|
||||||
public SquareFinderView(Context context) {
|
|
||||||
super(context);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SquareFinderView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
setSquareViewFinder(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +1,79 @@
|
||||||
package com.beemdevelopment.aegis.ui;
|
package com.beemdevelopment.aegis.ui;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.hardware.Camera;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.camera.core.CameraInfoUnavailableException;
|
||||||
|
import androidx.camera.core.CameraSelector;
|
||||||
|
import androidx.camera.core.ImageAnalysis;
|
||||||
|
import androidx.camera.core.Preview;
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||||
|
import androidx.camera.view.PreviewView;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.Theme;
|
import com.beemdevelopment.aegis.Theme;
|
||||||
import com.beemdevelopment.aegis.helpers.SquareFinderView;
|
import com.beemdevelopment.aegis.helpers.QrCodeAnalyzer;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.zxing.Result;
|
import com.google.zxing.Result;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import me.dm7.barcodescanner.core.IViewFinder;
|
public class ScannerActivity extends AegisActivity implements QrCodeAnalyzer.Listener {
|
||||||
import me.dm7.barcodescanner.zxing.ZXingScannerView;
|
private ProcessCameraProvider _cameraProvider;
|
||||||
|
private ListenableFuture<ProcessCameraProvider> _cameraProviderFuture;
|
||||||
|
|
||||||
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
|
private List<Integer> _lenses;
|
||||||
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
|
private int _currentLens;
|
||||||
|
|
||||||
public class ScannerActivity extends AegisActivity implements ZXingScannerView.ResultHandler {
|
|
||||||
private ZXingScannerView _scannerView;
|
|
||||||
private Menu _menu;
|
private Menu _menu;
|
||||||
private int _facing = CAMERA_FACING_BACK;
|
private PreviewView _previewView;
|
||||||
|
|
||||||
private int _batchId = 0;
|
private int _batchId = 0;
|
||||||
private int _batchIndex = -1;
|
private int _batchIndex = -1;
|
||||||
private List<VaultEntry> _entries;
|
private List<VaultEntry> _entries;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle state) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(state);
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_scanner);
|
||||||
|
|
||||||
_entries = new ArrayList<>();
|
_entries = new ArrayList<>();
|
||||||
_scannerView = new ZXingScannerView(this) {
|
_lenses = new ArrayList<>();
|
||||||
@Override
|
_previewView = findViewById(R.id.preview_view);
|
||||||
protected IViewFinder createViewFinderView(Context context) {
|
|
||||||
return new SquareFinderView(context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_scannerView.setResultHandler(this);
|
|
||||||
_scannerView.setFormats(Collections.singletonList(BarcodeFormat.QR_CODE));
|
|
||||||
|
|
||||||
int camera = getRearCameraId();
|
_cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
||||||
if (camera == -1) {
|
_cameraProviderFuture.addListener(() -> {
|
||||||
camera = getFrontCameraId();
|
try {
|
||||||
if (camera == -1) {
|
_cameraProvider = _cameraProviderFuture.get();
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
// if we're to believe the Android documentation, this should never happen
|
||||||
|
// https://developer.android.com/training/camerax/preview#check-provider
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCamera(CameraSelector.LENS_FACING_BACK);
|
||||||
|
addCamera(CameraSelector.LENS_FACING_FRONT);
|
||||||
|
if (_lenses.size() == 0) {
|
||||||
Toast.makeText(this, getString(R.string.no_cameras_available), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, getString(R.string.no_cameras_available), Toast.LENGTH_LONG).show();
|
||||||
finish();
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
_facing = CAMERA_FACING_FRONT;
|
_currentLens = _lenses.get(0);
|
||||||
}
|
updateCameraIcon();
|
||||||
_scannerView.startCamera(camera);
|
|
||||||
|
|
||||||
setContentView(_scannerView);
|
bindPreview(_cameraProvider);
|
||||||
|
}, ContextCompat.getMainExecutor(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,60 +85,80 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
_menu = menu;
|
_menu = menu;
|
||||||
getMenuInflater().inflate(R.menu.menu_scanner, menu);
|
getMenuInflater().inflate(R.menu.menu_scanner, menu);
|
||||||
updateCameraIcon();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.action_camera) {
|
||||||
case R.id.action_camera:
|
_cameraProvider.unbindAll();
|
||||||
_scannerView.stopCamera();
|
_currentLens = _currentLens == CameraSelector.LENS_FACING_BACK ? CameraSelector.LENS_FACING_FRONT : CameraSelector.LENS_FACING_BACK;
|
||||||
switch (_facing) {
|
bindPreview(_cameraProvider);
|
||||||
case CAMERA_FACING_BACK:
|
updateCameraIcon();
|
||||||
_facing = CAMERA_FACING_FRONT;
|
return true;
|
||||||
break;
|
}
|
||||||
case CAMERA_FACING_FRONT:
|
|
||||||
_facing = CAMERA_FACING_BACK;
|
return super.onOptionsItemSelected(item);
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
updateCameraIcon();
|
private void addCamera(int lens) {
|
||||||
_scannerView.startCamera(getCameraId(_facing));
|
try {
|
||||||
return true;
|
CameraSelector camera = new CameraSelector.Builder().requireLensFacing(lens).build();
|
||||||
case R.id.action_lock:
|
if (_cameraProvider.hasCamera(camera)) {
|
||||||
default:
|
_lenses.add(lens);
|
||||||
return super.onOptionsItemSelected(item);
|
}
|
||||||
|
} catch (CameraInfoUnavailableException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void updateCameraIcon() {
|
||||||
public void onResume() {
|
if (_menu != null) {
|
||||||
super.onResume();
|
MenuItem item = _menu.findItem(R.id.action_camera);
|
||||||
_scannerView.startCamera(getCameraId(_facing));
|
boolean dual = _lenses.size() > 1;
|
||||||
|
if (dual) {
|
||||||
|
switch (_currentLens) {
|
||||||
|
case CameraSelector.LENS_FACING_BACK:
|
||||||
|
item.setIcon(R.drawable.ic_camera_front_24dp);
|
||||||
|
break;
|
||||||
|
case CameraSelector.LENS_FACING_FRONT:
|
||||||
|
item.setIcon(R.drawable.ic_camera_rear_24dp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.setVisible(dual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
|
||||||
|
Preview preview = new Preview.Builder().build();
|
||||||
|
preview.setSurfaceProvider(_previewView.createSurfaceProvider());
|
||||||
|
|
||||||
|
CameraSelector selector = new CameraSelector.Builder()
|
||||||
|
.requireLensFacing(_currentLens)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ImageAnalysis analysis = new ImageAnalysis.Builder()
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.build();
|
||||||
|
analysis.setAnalyzer(ContextCompat.getMainExecutor(this), new QrCodeAnalyzer(this));
|
||||||
|
|
||||||
|
cameraProvider.bindToLifecycle(this, selector, preview, analysis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onQrCodeDetected(Result result) {
|
||||||
super.onPause();
|
|
||||||
_scannerView.stopCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleResult(Result rawResult) {
|
|
||||||
try {
|
try {
|
||||||
Uri uri = Uri.parse(rawResult.getText().trim());
|
Uri uri = Uri.parse(result.getText().trim());
|
||||||
if (uri.getScheme() != null && uri.getScheme().equals(GoogleAuthInfo.SCHEME_EXPORT)) {
|
if (uri.getScheme() != null && uri.getScheme().equals(GoogleAuthInfo.SCHEME_EXPORT)) {
|
||||||
handleExportUri(uri);
|
handleExportUri(uri);
|
||||||
} else {
|
} else {
|
||||||
handleUri(uri);
|
handleUri(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
_scannerView.resumeCameraPreview(this);
|
|
||||||
} catch (GoogleAuthInfoException e) {
|
} catch (GoogleAuthInfoException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialogs.showErrorDialog(this, R.string.read_qr_error, e, (dialog, which) -> {
|
Dialogs.showErrorDialog(this, R.string.read_qr_error, e, ((dialog, which) -> bindPreview(_cameraProvider)));
|
||||||
_scannerView.resumeCameraPreview(this);
|
_cameraProvider.unbindAll();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,42 +202,4 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
|
||||||
setResult(RESULT_OK, intent);
|
setResult(RESULT_OK, intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCameraIcon() {
|
|
||||||
if (_menu != null) {
|
|
||||||
MenuItem item = _menu.findItem(R.id.action_camera);
|
|
||||||
boolean dual = getFrontCameraId() != -1 && getRearCameraId() != -1;
|
|
||||||
if (dual) {
|
|
||||||
switch (_facing) {
|
|
||||||
case CAMERA_FACING_BACK:
|
|
||||||
item.setIcon(R.drawable.ic_camera_front_24dp);
|
|
||||||
break;
|
|
||||||
case CAMERA_FACING_FRONT:
|
|
||||||
item.setIcon(R.drawable.ic_camera_rear_24dp);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.setVisible(dual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getCameraId(int facing) {
|
|
||||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
|
||||||
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
|
|
||||||
Camera.getCameraInfo(i, info);
|
|
||||||
if (info.facing == facing) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getRearCameraId() {
|
|
||||||
return getCameraId(CAMERA_FACING_BACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getFrontCameraId() {
|
|
||||||
return getCameraId(CAMERA_FACING_FRONT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/activity_scanner"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
tools:context="com.beemdevelopment.aegis.ui.ScannerActivity">
|
tools:context="com.beemdevelopment.aegis.ui.ScannerActivity">
|
||||||
|
|
||||||
</RelativeLayout>
|
<androidx.camera.view.PreviewView
|
||||||
|
android:id="@+id/preview_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue