summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
authorNorbel AMBANUMBEN <aanorbel@gmail.com>2024-10-23 03:19:58 +0100
committerNorbel AMBANUMBEN <aanorbel@gmail.com>2024-10-23 03:19:58 +0100
commitbc0ae60a26dd0c2b06ea42b2314d3aac7d9e7508 (patch)
tree25aa77cfc91d26d03fd4ebb0bb6d84cc4f9b3eb0 /app/src/main
parent3d1215f6d8e047421520590f78c78c67e3ac3891 (diff)
update: camera scanner implementation
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java213
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerViewModel.java42
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java19
-rw-r--r--app/src/main/res/layout/a_scanner.xml15
4 files changed, 132 insertions, 157 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java
index e3c081c2..0878bd11 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java
@@ -1,41 +1,55 @@
package se.leap.bitmaskclient.providersetup.activities.scanner;
+import static androidx.appcompat.app.ActionBar.DISPLAY_SHOW_CUSTOM;
+import static androidx.camera.core.ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED;
+
+import android.Manifest;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
+import android.view.Gravity;
+import android.view.MenuItem;
import android.widget.Toast;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
+import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.camera.core.CameraSelector;
-import androidx.camera.core.ImageAnalysis;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Preview;
-import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.lifecycle.ViewModelProvider;
+import androidx.camera.mlkit.vision.MlKitAnalyzer;
+import androidx.camera.view.LifecycleCameraController;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.content.res.ResourcesCompat;
import com.google.mlkit.vision.barcode.BarcodeScanner;
+import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
import com.google.mlkit.vision.barcode.BarcodeScanning;
import com.google.mlkit.vision.barcode.common.Barcode;
-import com.google.mlkit.vision.common.InputImage;
+import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.models.Introducer;
+import se.leap.bitmaskclient.base.utils.ViewHelper;
+import se.leap.bitmaskclient.base.views.ActionBarTitle;
import se.leap.bitmaskclient.databinding.AScannerBinding;
-@OptIn(markerClass = androidx.camera.core.ExperimentalGetImage.class)
public class ScannerActivity extends AppCompatActivity {
public static final String INVITE_CODE = "invite_code";
private static final String TAG = ScannerActivity.class.getSimpleName();
+ private static final String[] REQUIRED_PERMISSIONS = new String[]{Manifest.permission.CAMERA};
+ private static final int REQUEST_CODE_PERMISSIONS = 1;
+
private AScannerBinding binding;
- private ProcessCameraProvider cameraProvider;
- private Preview previewUseCase;
- private CameraSelector cameraSelector;
- private ImageAnalysis analysisUseCase;
+ private BarcodeScanner barcodeScanner;
+ private ExecutorService cameraExecutor;
public static Intent newIntent(Context context) {
return new Intent(context, ScannerActivity.class);
@@ -46,105 +60,114 @@ public class ScannerActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
binding = AScannerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- initCamera();
- }
-
- private void initCamera() {
- cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
- new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(ScannerViewModel.class).getProcessCameraProvider().observe(this, cameraProvider -> {
- this.cameraProvider = cameraProvider;
- bindCameraUseCases();
- });
- }
+ setupActionBar();
+ if (allPermissionsGranted()) {
+ startCamera();
+ } else {
+ ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
+ }
+ cameraExecutor = Executors.newSingleThreadExecutor();
- private void bindCameraUseCases() {
- bindPreviewUseCase();
- bindAnalyseUseCase();
}
-
- private void bindPreviewUseCase() {
- if (cameraProvider == null) {
- return;
- }
- if (previewUseCase != null) {
- cameraProvider.unbind(previewUseCase);
+ private void setupActionBar() {
+ setSupportActionBar(binding.toolbar);
+ final ActionBar actionBar = getSupportActionBar();
+ Context context = actionBar.getThemedContext();
+ actionBar.setDisplayOptions(DISPLAY_SHOW_CUSTOM);
+
+ ActionBarTitle actionBarTitle = new ActionBarTitle(context);
+ actionBarTitle.setTitleCaps(BuildConfig.actionbar_capitalize_title);
+ actionBarTitle.setTitle("Scan QR Code");
+
+ final Drawable upArrow = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_back, getTheme());
+ actionBar.setHomeAsUpIndicator(upArrow);
+
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ ViewHelper.setActivityBarColor(this, R.color.bg_setup_status_bar, R.color.bg_setup_action_bar, R.color.colorActionBarTitleFont);
+ @ColorInt int titleColor = ContextCompat.getColor(context, R.color.colorActionBarTitleFont);
+ actionBarTitle.setTitleTextColor(titleColor);
+
+ actionBarTitle.setCentered(BuildConfig.actionbar_center_title);
+ actionBarTitle.setSingleBoldTitle();
+ if (BuildConfig.actionbar_center_title) {
+ ActionBar.LayoutParams params = new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.MATCH_PARENT, Gravity.CENTER);
+ actionBar.setCustomView(actionBarTitle, params);
+ } else {
+ actionBar.setCustomView(actionBarTitle);
}
+ }
- previewUseCase = new Preview.Builder().setTargetRotation(binding.previewView.getDisplay().getRotation()).build();
- previewUseCase.setSurfaceProvider(binding.previewView.getSurfaceProvider());
-
- try {
- cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase);
- } catch (Exception exception) {
- Log.e(TAG, exception.getMessage());
+ private boolean allPermissionsGranted() {
+ for (String permission : REQUIRED_PERMISSIONS) {
+ if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
}
+ return true;
}
+ private void startCamera() {
+ var cameraController = new LifecycleCameraController(getBaseContext());
+ var previewView = binding.previewView;
- private void bindAnalyseUseCase() {
- // Note that if you know which format of barcode your app is dealing with, detection will be
- // faster to specify the supported barcode formats one by one, e.g.
- // BarcodeScannerOptions.Builder()
- // .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
- // .build();
- BarcodeScanner barcodeScanner = BarcodeScanning.getClient();
+ var options = new BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_QR_CODE).build();
+ barcodeScanner = BarcodeScanning.getClient(options);
- if (cameraProvider == null) {
- return;
- }
- if (analysisUseCase != null) {
- cameraProvider.unbind(analysisUseCase);
- }
+ cameraController.setImageAnalysisAnalyzer(ContextCompat.getMainExecutor(this), new MlKitAnalyzer(Collections.singletonList(barcodeScanner), COORDINATE_SYSTEM_VIEW_REFERENCED, ContextCompat.getMainExecutor(this), result -> {
+ var barcodeResults = result.getValue(barcodeScanner);
+ if ((barcodeResults == null) || (barcodeResults.size() == 0) || (barcodeResults.get(0) == null)) {
+ previewView.getOverlay().clear();
+ previewView.setOnTouchListener((v, event) -> false); //no-op
+ return;
+ }
+ try {
+ Introducer introducer = Introducer.fromUrl(barcodeResults.get(0).getRawValue());
+ if (introducer != null && introducer.validate()) {
+ setResult(RESULT_OK, new Intent().putExtra(INVITE_CODE, introducer));
+ } else {
+ Toast.makeText(this, "Invalid introducer", Toast.LENGTH_SHORT).show();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ Toast.makeText(this, "Invalid introducer", Toast.LENGTH_SHORT).show();
+ }
+ previewView.setOnTouchListener((v, event) -> false); //no-op
+ previewView.getOverlay().clear();
- analysisUseCase = new ImageAnalysis.Builder().setTargetRotation(binding.previewView.getDisplay().getRotation()).build();
+ finish();
+ }));
- // Initialize our background executor
- ExecutorService cameraExecutor = Executors.newSingleThreadExecutor();
+ cameraController.bindToLifecycle(this);
+ previewView.setController(cameraController);
+ }
- analysisUseCase.setAnalyzer(cameraExecutor, imageProxy -> {
- // Insert barcode scanning code here
- processImageProxy(barcodeScanner, imageProxy);
- });
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ cameraExecutor.shutdown();
+ barcodeScanner.close();
+ }
- try {
- cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase);
- } catch (Exception exception) {
- Log.e(TAG, exception.getMessage());
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
}
+ return super.onOptionsItemSelected(item);
}
- private void processImageProxy(BarcodeScanner barcodeScanner, ImageProxy imageProxy) {
- InputImage inputImage = InputImage.fromMediaImage(imageProxy.getImage(), imageProxy.getImageInfo().getRotationDegrees());
-
- barcodeScanner.process(inputImage).addOnSuccessListener(barcodes -> {
- for (Barcode barcode : barcodes) {
- try {
- Introducer introducer = Introducer.fromUrl(barcode.getRawValue());
- if (introducer != null && introducer.validate()) {
- imageProxy.close();
- setResult(RESULT_OK, new Intent().putExtra(INVITE_CODE, introducer));
- finish();
- } else {
- Toast.makeText(this, "Invalid introducer", Toast.LENGTH_SHORT).show();
- }
- } catch (Exception e) {
- Log.e(TAG, e.getMessage());
- Toast.makeText(this, "Invalid introducer", Toast.LENGTH_SHORT).show();
- }
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_CODE_PERMISSIONS) {
+ if (allPermissionsGranted()) {
+ startCamera();
+ } else {
+ Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
+ finish();
}
- }).addOnFailureListener(e -> {
- Log.e(TAG, e.getMessage());
- }).addOnCompleteListener(task -> {
- // When the image is from CameraX analysis use case, must call image.close() on received
- // images when finished using them. Otherwise, new images may not be received or the camera
- // may stall.
- imageProxy.close();
- });
+ }
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerViewModel.java
deleted file mode 100644
index d749e512..00000000
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerViewModel.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package se.leap.bitmaskclient.providersetup.activities.scanner;
-
-import android.app.Application;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.core.content.ContextCompat;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-
-public class ScannerViewModel extends AndroidViewModel {
- private static final String TAG = ScannerViewModel.class.getSimpleName();
- private MutableLiveData<ProcessCameraProvider> cameraProviderLiveData;
-
- public ScannerViewModel(@NonNull Application application) {
- super(application);
- }
-
- public LiveData<ProcessCameraProvider> getProcessCameraProvider() {
- if (cameraProviderLiveData == null) {
- cameraProviderLiveData = new MutableLiveData<>();
- ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getApplication());
- cameraProviderFuture.addListener(() -> {
- try {
- cameraProviderLiveData.setValue(cameraProviderFuture.get());
- } catch (ExecutionException e) {
- // Handle any errors (including cancellation) here.
- Log.e(TAG, "Unhandled exception", e);
- } catch (InterruptedException e) {
- Log.e(TAG, "Unhandled exception", e);
- }
- }, ContextCompat.getMainExecutor(getApplication()));
- }
- return cameraProviderLiveData;
- }
-}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
index 8ccf7993..47f0e972 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
@@ -38,7 +38,6 @@ import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelection
public class ProviderSelectionFragment extends BaseSetupFragment implements CancelCallback {
- private static final int PERMISSION_GRANTED_REQUEST_CODE = 1;
private ActivityResultLauncher<Intent> scannerActivityResultLauncher;
private ProviderSelectionViewModel viewModel;
@@ -112,25 +111,9 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc
}
private void initQrScanner() {
- binding.qrScanner.setOnClickListener(v -> {
- if (isCameraPermissionGranted()) {
- scannerActivityResultLauncher.launch(ScannerActivity.newIntent(getContext()));
- } else {
- ActivityCompat.requestPermissions(
- getActivity(),
- new String[]{Manifest.permission.CAMERA},
- PERMISSION_GRANTED_REQUEST_CODE
- );
- }
- });
+ binding.qrScanner.setOnClickListener(v -> scannerActivityResultLauncher.launch(ScannerActivity.newIntent(getContext())));
}
- private boolean isCameraPermissionGranted() {
- return ContextCompat.checkSelfPermission(
- getContext(),
- Manifest.permission.CAMERA
- ) == PackageManager.PERMISSION_GRANTED;
- }
@Override
public void onFragmentSelected() {
diff --git a/app/src/main/res/layout/a_scanner.xml b/app/src/main/res/layout/a_scanner.xml
index 5fb2db53..bf954d99 100644
--- a/app/src/main/res/layout/a_scanner.xml
+++ b/app/src/main/res/layout/a_scanner.xml
@@ -6,13 +6,24 @@
android:layout_height="match_parent"
tools:context=".providersetup.activities.scanner.ScannerActivity">
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:minHeight="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/colorPrimary"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+ </androidx.appcompat.widget.Toolbar>
+
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file