From 1df58041a291d451438f67366c7c1605f2461fa9 Mon Sep 17 00:00:00 2001
From: Norbel Ambanumben <norbel@ambanumben.net>
Date: Sat, 18 Jan 2025 20:04:06 +0100
Subject: [PATCH] feat: replace `mlkit` with `zxing`

---
 app/build.gradle                              |  13 +-
 app/src/main/AndroidManifest.xml              |   7 +-
 .../activities/scanner/ScannerActivity.java   | 183 ------------------
 .../fragments/ProviderSelectionFragment.java  |  37 ++--
 app/src/main/res/layout/a_scanner.xml         |  29 ---
 5 files changed, 29 insertions(+), 240 deletions(-)
 delete mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java
 delete mode 100644 app/src/main/res/layout/a_scanner.xml

diff --git a/app/build.gradle b/app/build.gradle
index ccd54dc94..0085eca8a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -435,16 +435,9 @@ dependencies {
   implementation 'io.gsonfire:gson-fire:1.8.0'
   implementation 'org.threeten:threetenbp:1.3.5'
 
-
-  // Start:: CameraX dependencies
-  implementation 'com.google.mlkit:barcode-scanning:17.3.0'
-  implementation 'androidx.camera:camera-core:1.3.4'
-  implementation 'androidx.camera:camera-camera2:1.3.4'
-  implementation 'androidx.camera:camera-lifecycle:1.3.4'
-  implementation 'androidx.camera:camera-view:1.3.4'
-  implementation 'androidx.camera:camera-mlkit-vision:1.4.0-rc04'
-  // End:: CameraX dependencies
-
+  // Start:: QR Code Scanner
+  implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
+  // End:: QR Code Scanner
 
   //implementation 'info.guardianproject:tor-android:0.4.5.7'
   //implementation 'info.guardianproject:jtorctl:0.4.5.7'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3a7621809..6759862ef 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -114,10 +114,11 @@
                 <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
             </intent-filter>
         </activity>
-        <activity android:name=".providersetup.activities.scanner.ScannerActivity"
-            android:launchMode="singleInstance"
+
+        <activity android:name="com.journeyapps.barcodescanner.CaptureActivity"
             android:screenOrientation="portrait"
-            android:exported="false" />
+            tools:replace="screenOrientation" />
+
         <activity
             android:name=".base.MainActivity"
             android:label="@string/app_name"
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
deleted file mode 100644
index 1d828d8c2..000000000
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/scanner/ScannerActivity.java
+++ /dev/null
@@ -1,183 +0,0 @@
-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.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.camera.mlkit.vision.MlKitAnalyzer;
-import androidx.camera.view.LifecycleCameraController;
-import androidx.camera.view.PreviewView;
-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 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;
-
-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 BarcodeScanner barcodeScanner;
-    private ExecutorService cameraExecutor;
-
-    public static Intent newIntent(Context context) {
-        return new Intent(context, ScannerActivity.class);
-    }
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        binding = AScannerBinding.inflate(getLayoutInflater());
-        setContentView(binding.getRoot());
-        setupActionBar();
-        if (allPermissionsGranted()) {
-            startCamera();
-        } else {
-            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
-        }
-        cameraExecutor = Executors.newSingleThreadExecutor();
-
-    }
-
-    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(getString(R.string.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);
-        }
-    }
-
-    private void startCamera() {
-        var cameraController = new LifecycleCameraController(getBaseContext());
-        var previewView = binding.previewView;
-
-        var options = new BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_QR_CODE).build();
-        barcodeScanner = BarcodeScanning.getClient(options);
-
-        var mlKitAnalyzer = new MlKitAnalyzer(
-                Collections.singletonList(barcodeScanner),
-                COORDINATE_SYSTEM_VIEW_REFERENCED,
-                ContextCompat.getMainExecutor(this),
-                result -> handleQrResult(result, previewView)
-        );
-
-        cameraController.setImageAnalysisAnalyzer(ContextCompat.getMainExecutor(this), mlKitAnalyzer);
-
-        cameraController.bindToLifecycle(this);
-        previewView.setController(cameraController);
-    }
-
-    private void handleQrResult(MlKitAnalyzer.Result result, PreviewView previewView) {
-        var barcodeResults = result.getValue(barcodeScanner);
-        if ((barcodeResults == null) || (barcodeResults.isEmpty()) || (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.validate()) {
-                setResult(RESULT_OK, new Intent().putExtra(INVITE_CODE, introducer));
-            } else {
-                Toast.makeText(this, R.string.invalid_code, Toast.LENGTH_SHORT).show();
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Introducer error: " + e.getMessage());
-            Toast.makeText(this, R.string.invalid_code, Toast.LENGTH_SHORT).show();
-        }
-        previewView.setOnTouchListener((v, event) -> false); //no-op
-        previewView.getOverlay().clear();
-
-        finish();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        cameraExecutor.shutdown();
-        barcodeScanner.close();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
-        if (item.getItemId() == android.R.id.home) {
-            finish();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    private boolean allPermissionsGranted() {
-        for (String permission : REQUIRED_PERMISSIONS) {
-            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @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();
-            }
-        }
-    }
-}
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 e4302be5a..3a75925e6 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
@@ -3,8 +3,6 @@ package se.leap.bitmaskclient.providersetup.fragments;
 import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.ADD_PROVIDER;
 import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.INVITE_CODE_PROVIDER;
 
-import android.app.Activity;
-import android.content.Intent;
 import android.graphics.Typeface;
 import android.os.Bundle;
 import android.text.Editable;
@@ -15,11 +13,13 @@ import android.view.ViewGroup;
 import android.widget.RadioButton;
 
 import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.lifecycle.ViewModelProvider;
 
+import com.journeyapps.barcodescanner.ScanContract;
+import com.journeyapps.barcodescanner.ScanOptions;
+
 import java.util.ArrayList;
 
 import se.leap.bitmaskclient.R;
@@ -28,13 +28,12 @@ import se.leap.bitmaskclient.base.models.Provider;
 import se.leap.bitmaskclient.base.utils.ViewHelper;
 import se.leap.bitmaskclient.databinding.FProviderSelectionBinding;
 import se.leap.bitmaskclient.providersetup.activities.CancelCallback;
-import se.leap.bitmaskclient.providersetup.activities.scanner.ScannerActivity;
 import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel;
 import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory;
 
 public class ProviderSelectionFragment extends BaseSetupFragment implements CancelCallback {
 
-    private ActivityResultLauncher<Intent> scannerActivityResultLauncher;
+    private ActivityResultLauncher<ScanOptions> scannerActivityResultLauncher;
 
     private ProviderSelectionViewModel viewModel;
     private ArrayList<RadioButton> radioButtons;
@@ -55,15 +54,17 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc
                 new ProviderSelectionViewModelFactory(
                         getContext().getApplicationContext().getAssets())).
                 get(ProviderSelectionViewModel.class);
-        scannerActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
-            if (result.getResultCode() == Activity.RESULT_OK) {
-                Intent data = result.getData();
-                if (data != null) {
-                    Introducer introducer = data.getParcelableExtra(ScannerActivity.INVITE_CODE);
-                    binding.editCustomProvider.setText(introducer.toUrl());
-                }
-            }
-        });
+
+        scannerActivityResultLauncher = registerForActivityResult(new ScanContract(), result -> {
+                    if(result.getContents() != null) {
+                        try {
+                            Introducer introducer = Introducer.fromUrl(result.getContents());
+                            binding.editCustomProvider.setText(introducer.toUrl());
+                        } catch (Exception e) {
+                            //binding.editCustomProvider.setText(result.getContents());
+                        }
+                    }
+                });
     }
 
     @Override
@@ -107,7 +108,13 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc
     }
 
     private void initQrScanner() {
-        binding.btnQrScanner.setOnClickListener(v -> scannerActivityResultLauncher.launch(ScannerActivity.newIntent(getContext())));
+        binding.btnQrScanner.setOnClickListener(v -> {
+            ScanOptions options = new ScanOptions();
+            options.setBeepEnabled(false);
+            options.setBarcodeImageEnabled(true);
+            options.setOrientationLocked(false);
+            scannerActivityResultLauncher.launch(options);
+        });
     }
 
 
diff --git a/app/src/main/res/layout/a_scanner.xml b/app/src/main/res/layout/a_scanner.xml
deleted file mode 100644
index bf954d990..000000000
--- a/app/src/main/res/layout/a_scanner.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<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"
-    android:layout_width="match_parent"
-    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="0dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/toolbar" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
-- 
GitLab