From 95137c1b5e3c5febead0a9ad461f7e3a57e52019 Mon Sep 17 00:00:00 2001 From: jonathanbataire Date: Wed, 2 Oct 2024 16:04:34 +0300 Subject: [PATCH 01/12] :tada: --- build.gradle | 5 + src/main/AndroidManifest.xml | 12 ++ .../webapp/mobile/BarcodeScanner.java | 141 ++++++++++++++++++ .../webapp/mobile/ChtExternalAppHandler.java | 5 + .../mobile/EmbeddedBrowserActivity.java | 11 +- .../webapp/mobile/MedicAndroidJavascript.java | 37 +++-- src/main/res/layout/barcode_scanner.xml | 15 ++ 7 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java create mode 100644 src/main/res/layout/barcode_scanner.xml diff --git a/build.gradle b/build.gradle index 5e4f4afb..1d806bc1 100644 --- a/build.gradle +++ b/build.gradle @@ -477,6 +477,11 @@ android { } dependencies { + implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1' + def cameraXVersion = '1.3.4' + implementation "androidx.camera:camera-camera2:${cameraXVersion}" + implementation "androidx.camera:camera-lifecycle:${cameraXVersion}" + implementation "androidx.camera:camera-view:${cameraXVersion}" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation platform('org.jetbrains.kotlin:kotlin-bom:1.9.24') implementation 'androidx.core:core:1.13.1' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index d7c45d01..a6cee7ff 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -11,6 +11,9 @@ + @@ -21,6 +24,9 @@ + + + @@ -32,6 +38,9 @@ android:dataExtractionRules="@xml/backup_rules" android:largeHeap="true" tools:ignore="LockedOrientationActivity,UnusedAttribute"> + @@ -65,6 +74,9 @@ + diff --git a/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java b/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java new file mode 100644 index 00000000..62858003 --- /dev/null +++ b/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java @@ -0,0 +1,141 @@ +package org.medicmobile.webapp.mobile; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.OptIn; +import androidx.appcompat.app.AppCompatActivity; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.ExperimentalGetImage; +import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.ImageProxy; +import androidx.camera.core.Preview; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.camera.view.PreviewView; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.google.android.gms.tasks.Task; +import com.google.common.util.concurrent.ListenableFuture; +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.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import static org.medicmobile.webapp.mobile.EmbeddedBrowserActivity.RequestCode; + +public class BarcodeScanner extends AppCompatActivity { + + private PreviewView scannerPreview; + private ExecutorService cameraExecutor; + private static final String TAG = "BarcodeScanner"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.barcode_scanner); + scannerPreview = findViewById(R.id.scannerPreview); + cameraExecutor = Executors.newSingleThreadExecutor(); + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + startCamera(); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.CAMERA}, + RequestCode.BARCODE_SCANNER.getCode()); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + cameraExecutor.shutdown(); + } + + private void startCamera () { + ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this); + cameraProviderFuture.addListener(() -> { + try { + ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); + bindPreview(cameraProvider); + } catch (ExecutionException | InterruptedException e){ + Log.e(TAG, Objects.requireNonNull(e.getMessage())); + } + }, + ContextCompat.getMainExecutor(this)); + } + + private void bindPreview(ProcessCameraProvider cameraProvider){ + Preview preview = new Preview.Builder().build(); + preview.setSurfaceProvider(scannerPreview.getSurfaceProvider()); + CameraSelector cameraSelector = new CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build(); + + ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build(); + imageAnalysis.setAnalyzer(cameraExecutor, this::imageAnalyzer); + + cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis); + } + + + @OptIn(markerClass = ExperimentalGetImage.class) + private void imageAnalyzer(ImageProxy imageProxy) { + + if (imageProxy.getImage() != null) { + InputImage image = InputImage.fromMediaImage(imageProxy.getImage(), imageProxy.getImageInfo().getRotationDegrees()); + BarcodeScannerOptions options = + new BarcodeScannerOptions.Builder() + .setBarcodeFormats( + //formats to support + Barcode.FORMAT_QR_CODE, + Barcode.FORMAT_AZTEC, + Barcode.FORMAT_CODE_39, + Barcode.FORMAT_PDF417 + ) + .build(); + com.google.mlkit.vision.barcode.BarcodeScanner scanner = BarcodeScanning.getClient(options); + + Task> result = scanner.process(image); + result.addOnSuccessListener(barcodes -> { + for (Barcode barcode : barcodes) { + String rawValue = barcode.getRawValue(); + if (rawValue != null) { + returnIntent(rawValue); + } + } + }); + result.addOnFailureListener(e -> { + Log.e(TAG, Objects.requireNonNull(e.getMessage())); + Toast.makeText(this, "Failed to scan image", Toast.LENGTH_SHORT).show(); + }); + + result.addOnCompleteListener(task -> { + imageProxy.close(); + }); + } + } + + private void returnIntent(String barcode) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("CHT_QR_CODE", barcode); + setResult(RESULT_OK, resultIntent); + finish(); + } +} diff --git a/src/main/java/org/medicmobile/webapp/mobile/ChtExternalAppHandler.java b/src/main/java/org/medicmobile/webapp/mobile/ChtExternalAppHandler.java index 2998a5d5..a8880696 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/ChtExternalAppHandler.java +++ b/src/main/java/org/medicmobile/webapp/mobile/ChtExternalAppHandler.java @@ -76,6 +76,11 @@ void resumeActivity(int resultCode) { this.lastIntent = null; // Cleaning as we don't need it anymore. } + void triggerScanner() { + Intent intent = new Intent(context, BarcodeScanner.class); + this.context.startActivityForResult(intent, RequestCode.BARCODE_SCANNER.getCode()); + } + //> PRIVATE private void startActivity(Intent intent) { diff --git a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java index 96f4e318..1049886e 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java @@ -208,7 +208,9 @@ protected void onActivityResult(int requestCd, int resultCode, Intent intent) { case ACCESS_SEND_SMS_PERMISSION: this.smsSender.resumeProcess(resultCode); return; - default: + case BARCODE_SCANNER: + processChtBarcodeResult(resultCode, intent); + default: trace(this, "onActivityResult() :: no handling for requestCode=%s", requestCode.name()); } } catch (Exception ex) { @@ -283,6 +285,10 @@ private void locationRequestResolved() { evaluateJavascript("window.CHTCore.AndroidApi.v1.locationPermissionRequestResolved();"); } + private void processChtBarcodeResult (int resultCode, Intent intentData) { + processChtExternalAppResult(resultCode, intentData); + } + private void processChtExternalAppResult(int resultCode, Intent intentData) { String script = this.chtExternalAppHandler.processResult(resultCode, intentData); trace(this, "ChtExternalAppHandler :: Executing JavaScript: %s", script); @@ -410,7 +416,8 @@ public enum RequestCode { ACCESS_SEND_SMS_PERMISSION(102), CHT_EXTERNAL_APP_ACTIVITY(103), GRAB_MRDT_PHOTO_ACTIVITY(104), - FILE_PICKER_ACTIVITY(105); + FILE_PICKER_ACTIVITY(105), + BARCODE_SCANNER(106); private final int requestCode; diff --git a/src/main/java/org/medicmobile/webapp/mobile/MedicAndroidJavascript.java b/src/main/java/org/medicmobile/webapp/mobile/MedicAndroidJavascript.java index aac1e627..f96eff41 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/MedicAndroidJavascript.java +++ b/src/main/java/org/medicmobile/webapp/mobile/MedicAndroidJavascript.java @@ -31,6 +31,7 @@ import java.util.Calendar; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.json.JSONException; import org.json.JSONObject; @@ -189,21 +190,27 @@ public void sms_send(String id, String destination, String content) throws Excep @android.webkit.JavascriptInterface public void launchExternalApp(String action, String category, String type, String extras, String uri, String packageName, String flags) { try { - JSONObject parsedExtras = extras == null ? null : new JSONObject(extras); - Uri parsedUri = uri == null ? null : Uri.parse(uri); - Integer parsedFlags = flags == null ? null : Integer.parseInt(flags); - - ChtExternalApp chtExternalApp = new ChtExternalApp - .Builder() - .setAction(action) - .setCategory(category) - .setType(type) - .setExtras(parsedExtras) - .setUri(parsedUri) - .setPackageName(packageName) - .setFlags(parsedFlags) - .build(); - this.chtExternalAppHandler.startIntent(chtExternalApp); + //TODO: this implementation is hijacking this bridge + // Create a new webapp API specifically for this embedded barcode scanner + if(Objects.equals(action, "cht.android.SCAN_BARCODE")) { + this.chtExternalAppHandler.triggerScanner(); + } else { + JSONObject parsedExtras = extras == null ? null : new JSONObject(extras); + Uri parsedUri = uri == null ? null : Uri.parse(uri); + Integer parsedFlags = flags == null ? null : Integer.parseInt(flags); + + ChtExternalApp chtExternalApp = new ChtExternalApp + .Builder() + .setAction(action) + .setCategory(category) + .setType(type) + .setExtras(parsedExtras) + .setUri(parsedUri) + .setPackageName(packageName) + .setFlags(parsedFlags) + .build(); + this.chtExternalAppHandler.startIntent(chtExternalApp); + } } catch (Exception ex) { logException(ex); diff --git a/src/main/res/layout/barcode_scanner.xml b/src/main/res/layout/barcode_scanner.xml new file mode 100644 index 00000000..f1dbca37 --- /dev/null +++ b/src/main/res/layout/barcode_scanner.xml @@ -0,0 +1,15 @@ + + + + + + From 7cbdbacf4efad5d9e4e4bb811c27237cda210f80 Mon Sep 17 00:00:00 2001 From: jonathanbataire Date: Mon, 14 Oct 2024 17:49:38 +0300 Subject: [PATCH 02/12] clean up format --- .../org/medicmobile/webapp/mobile/BarcodeScanner.java | 11 +++++++---- .../webapp/mobile/EmbeddedBrowserActivity.java | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java b/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java index 62858003..9551b1ac 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java +++ b/src/main/java/org/medicmobile/webapp/mobile/BarcodeScanner.java @@ -3,12 +3,10 @@ import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Build; import android.os.Bundle; import android.util.Log; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.OptIn; import androidx.appcompat.app.AppCompatActivity; @@ -66,7 +64,7 @@ protected void onDestroy() { cameraExecutor.shutdown(); } - private void startCamera () { + private void startCamera() { ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { @@ -107,7 +105,12 @@ private void imageAnalyzer(ImageProxy imageProxy) { Barcode.FORMAT_QR_CODE, Barcode.FORMAT_AZTEC, Barcode.FORMAT_CODE_39, - Barcode.FORMAT_PDF417 + Barcode.FORMAT_PDF417, + Barcode.FORMAT_EAN_13, + Barcode.FORMAT_CODE_128, + Barcode.FORMAT_UPC_E, + Barcode.FORMAT_UPC_A, + Barcode.FORMAT_EAN_8 ) .build(); com.google.mlkit.vision.barcode.BarcodeScanner scanner = BarcodeScanning.getClient(options); diff --git a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java index 1049886e..7eb421c7 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java @@ -285,7 +285,7 @@ private void locationRequestResolved() { evaluateJavascript("window.CHTCore.AndroidApi.v1.locationPermissionRequestResolved();"); } - private void processChtBarcodeResult (int resultCode, Intent intentData) { + private void processChtBarcodeResult(int resultCode, Intent intentData) { processChtExternalAppResult(resultCode, intentData); } @@ -417,7 +417,7 @@ public enum RequestCode { CHT_EXTERNAL_APP_ACTIVITY(103), GRAB_MRDT_PHOTO_ACTIVITY(104), FILE_PICKER_ACTIVITY(105), - BARCODE_SCANNER(106); + BARCODE_SCANNER(106); private final int requestCode; From 99b849ca6518ddec0367a5ced373691f93deb317 Mon Sep 17 00:00:00 2001 From: jonathanbataire Date: Mon, 14 Oct 2024 17:53:27 +0300 Subject: [PATCH 03/12] clean up format 2 --- .../org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java index 7eb421c7..6f02909e 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java @@ -210,7 +210,7 @@ protected void onActivityResult(int requestCd, int resultCode, Intent intent) { return; case BARCODE_SCANNER: processChtBarcodeResult(resultCode, intent); - default: + default: trace(this, "onActivityResult() :: no handling for requestCode=%s", requestCode.name()); } } catch (Exception ex) { From 983d19e8892f0d5f5936e33e6e7d25abe6039920 Mon Sep 17 00:00:00 2001 From: jonathanbataire Date: Mon, 14 Oct 2024 18:05:20 +0300 Subject: [PATCH 04/12] clean up format xml --- src/main/res/layout/connection_error.xml | 7 +++++-- src/main/res/layout/custom_server_form.xml | 4 ++-- src/main/res/layout/free_space_warning.xml | 4 ++-- src/main/res/layout/request_app_domain_association.xml | 4 ++-- src/main/res/layout/request_location_permission.xml | 4 ++-- src/main/res/layout/request_send_sms_permission.xml | 4 ++-- src/main/res/layout/request_storage_permission.xml | 4 ++-- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/res/layout/connection_error.xml b/src/main/res/layout/connection_error.xml index 910509d1..cd91f105 100644 --- a/src/main/res/layout/connection_error.xml +++ b/src/main/res/layout/connection_error.xml @@ -1,5 +1,6 @@ + android:textAllCaps="true" + tools:ignore="UsingOnClickInXml" /> + android:textAllCaps="true" + tools:ignore="UsingOnClickInXml" /> diff --git a/src/main/res/layout/custom_server_form.xml b/src/main/res/layout/custom_server_form.xml index a0c34725..1f85d632 100644 --- a/src/main/res/layout/custom_server_form.xml +++ b/src/main/res/layout/custom_server_form.xml @@ -22,7 +22,7 @@ android:layout_alignParentBottom="true" android:onClick="cancelSettingsEdit" android:text="@string/btnCancel" - tools:ignore="OnClick" /> + tools:ignore="OnClick,UsingOnClickInXml" /> @@ -32,5 +32,5 @@ android:layout_alignParentBottom="true" android:onClick="verifyAndSave" android:text="@string/btnSave" - tools:ignore="OnClick" /> + tools:ignore="OnClick,UsingOnClickInXml" /> diff --git a/src/main/res/layout/free_space_warning.xml b/src/main/res/layout/free_space_warning.xml index 08203107..50192b3a 100644 --- a/src/main/res/layout/free_space_warning.xml +++ b/src/main/res/layout/free_space_warning.xml @@ -37,7 +37,7 @@ style="@style/standardButton" android:text="@string/btnQuit" android:onClick="evtQuit" - tools:ignore="OnClick" + tools:ignore="OnClick,UsingOnClickInXml" android:layout_toStartOf="@+id/btnFreeSpaceContinue" android:layout_alignParentBottom="true"/>