diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30486c6362..9e0943ad14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## Unreleased - 134
+
+- Support for scanning PDF files for barcodes
+- Support for image files with multiple barcodes
+
## v2.28.0 - 133 (2024-03-08)
- Target Android 14
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7c3db457ef..2f3c1c4c55 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,6 +44,7 @@
+
barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, getApplicationContext());
+
+ Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
+ @Override
+ public void onUserChoseBarcode(BarcodeValues barcodeValues) {
+ cardId = barcodeValues.content();
+ barcodeType = barcodeValues.format();
+ barcodeId = "";
+ }
+
+ @Override
+ public void onUserDismissedSelector() {
+
+ }
+ });
}
});
diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java
index 4f4b16b27f..69ab85ef64 100644
--- a/app/src/main/java/protect/card_locker/MainActivity.java
+++ b/app/src/main/java/protect/card_locker/MainActivity.java
@@ -7,8 +7,6 @@
import android.content.SharedPreferences;
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Bitmap;
-import android.net.Uri;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -33,7 +31,6 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -195,10 +192,12 @@ public void onDestroyActionMode(ActionMode inputMode) {
@Override
protected void onCreate(Bundle inputSavedInstanceState) {
- extractIntentFields(getIntent());
SplashScreen.installSplashScreen(this);
super.onCreate(inputSavedInstanceState);
+ // We should extract the share intent after we called the super.onCreate as it may need to spawn a dialog window and the app needs to be initialized to not crash
+ extractIntentFields(getIntent());
+
binding = MainActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
@@ -288,11 +287,11 @@ public void onClick(DialogInterface dialog, int whichButton) {
}
Intent intent = result.getData();
- BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
+ List barcodeValuesList = Utils.parseSetBarcodeActivityResult(Utils.BARCODE_SCAN, result.getResultCode(), intent, this);
Bundle inputBundle = intent.getExtras();
String group = inputBundle != null ? inputBundle.getString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP) : null;
- processBarcodeValues(barcodeValues, group);
+ processBarcodeValuesList(barcodeValuesList, group, false);
});
mSettingsLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
@@ -447,63 +446,57 @@ private void updateLoyaltyCardList(boolean updateCount) {
}
}
- private void processBarcodeValues(BarcodeValues barcodeValues, String group) {
- if (barcodeValues.isEmpty()) {
+ private void processBarcodeValuesList(List barcodeValuesList, String group, boolean closeAppOnNoBarcode) {
+ if (barcodeValuesList.isEmpty()) {
throw new IllegalArgumentException("barcodesValues may not be empty");
}
- Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
- Bundle newBundle = new Bundle();
- newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
- newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
- if (group != null) {
- newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
- }
- newIntent.putExtras(newBundle);
- startActivity(newIntent);
+ Utils.makeUserChooseBarcodeFromList(MainActivity.this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
+ @Override
+ public void onUserChoseBarcode(BarcodeValues barcodeValues) {
+ Intent newIntent = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
+ Bundle newBundle = new Bundle();
+ newBundle.putString(LoyaltyCardEditActivity.BUNDLE_BARCODETYPE, barcodeValues.format());
+ newBundle.putString(LoyaltyCardEditActivity.BUNDLE_CARDID, barcodeValues.content());
+ if (group != null) {
+ newBundle.putString(LoyaltyCardEditActivity.BUNDLE_ADDGROUP, group);
+ }
+ newIntent.putExtras(newBundle);
+ startActivity(newIntent);
+ }
+
+ @Override
+ public void onUserDismissedSelector() {
+ if (closeAppOnNoBarcode) {
+ finish();
+ }
+ }
+ });
}
private void onSharedIntent(Intent intent) {
String receivedAction = intent.getAction();
String receivedType = intent.getType();
- // Check if an image was shared to us
+ // Check if an image or file was shared to us
if (Intent.ACTION_SEND.equals(receivedAction)) {
- if (!receivedType.startsWith("image/")) {
- Log.e(TAG, "Wrong mime-type");
- return;
- }
-
- BarcodeValues barcodeValues;
- Bitmap bitmap;
+ List barcodeValuesList;
- Uri data = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- if (data == null) {
- Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
- finish();
- return;
- }
-
- try {
- bitmap = Utils.retrieveImageFromUri(this, data);
- } catch (IOException e) {
- Log.e(TAG, "Error getting data from image file");
- e.printStackTrace();
- Toast.makeText(this, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
- finish();
+ if (receivedType.startsWith("image/")) {
+ barcodeValuesList = Utils.retrieveBarcodesFromImage(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
+ } else if (receivedType.equals("application/pdf")) {
+ barcodeValuesList = Utils.retrieveBarcodesFromPdf(this, intent.getParcelableExtra(Intent.EXTRA_STREAM));
+ } else {
+ Log.e(TAG, "Wrong mime-type");
return;
}
- barcodeValues = Utils.getBarcodeFromBitmap(bitmap);
-
- if (barcodeValues.isEmpty()) {
- Log.i(TAG, "No barcode found in image file");
- Toast.makeText(this, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
+ if (barcodeValuesList.isEmpty()) {
finish();
return;
}
- processBarcodeValues(barcodeValues, null);
+ processBarcodeValuesList(barcodeValuesList, null, true);
}
}
diff --git a/app/src/main/java/protect/card_locker/ScanActivity.java b/app/src/main/java/protect/card_locker/ScanActivity.java
index 01fc3854dc..9679751bbc 100644
--- a/app/src/main/java/protect/card_locker/ScanActivity.java
+++ b/app/src/main/java/protect/card_locker/ScanActivity.java
@@ -62,6 +62,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
private static final int COMPAT_SCALE_FACTOR_DIP = 320;
private static final int PERMISSION_SCAN_ADD_FROM_IMAGE = 100;
+ private static final int PERMISSION_SCAN_ADD_FROM_PDF = 101;
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;
@@ -73,6 +74,7 @@ public class ScanActivity extends CatimaAppCompatActivity {
private ActivityResultLauncher manualAddLauncher;
// can't use the pre-made contract because that launches the file manager for image type instead of gallery
private ActivityResultLauncher photoPickerLauncher;
+ private ActivityResultLauncher pdfPickerLauncher;
static final String STATE_SCANNER_ACTIVE = "scannerActive";
private boolean mScannerActive = true;
@@ -99,6 +101,7 @@ protected void onCreate(Bundle savedInstanceState) {
manualAddLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.SELECT_BARCODE_REQUEST, result.getResultCode(), result.getData()));
photoPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_IMAGE_FILE, result.getResultCode(), result.getData()));
+ pdfPickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> handleActivityResult(Utils.BARCODE_IMPORT_FROM_PDF_FILE, result.getResultCode(), result.getData()));
customBarcodeScannerBinding.fabOtherOptions.setOnClickListener(view -> {
setScannerActive(false);
@@ -108,7 +111,8 @@ protected void onCreate(Bundle savedInstanceState) {
new CharSequence[]{
getString(R.string.addWithoutBarcode),
getString(R.string.addManually),
- getString(R.string.addFromImage)
+ getString(R.string.addFromImage),
+ getString(R.string.addFromPdfFile)
},
(dialogInterface, i) -> {
switch (i) {
@@ -121,6 +125,9 @@ protected void onCreate(Bundle savedInstanceState) {
case 2:
addFromImage();
break;
+ case 3:
+ addFromPdfFile();
+ break;
default:
throw new IllegalArgumentException("Unknown 'Add a card in a different way' dialog option");
}
@@ -268,14 +275,24 @@ private void returnResult(String barcodeContents, String barcodeFormat) {
private void handleActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
- BarcodeValues barcodeValues = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
+ List barcodeValuesList = Utils.parseSetBarcodeActivityResult(requestCode, resultCode, intent, this);
- if (barcodeValues.isEmpty()) {
+ if (barcodeValuesList.isEmpty()) {
setScannerActive(true);
return;
}
- returnResult(barcodeValues.content(), barcodeValues.format());
+ Utils.makeUserChooseBarcodeFromList(this, barcodeValuesList, new BarcodeValuesListDisambiguatorCallback() {
+ @Override
+ public void onUserChoseBarcode(BarcodeValues barcodeValues) {
+ returnResult(barcodeValues.content(), barcodeValues.format());
+ }
+
+ @Override
+ public void onUserDismissedSelector() {
+ setScannerActive(true);
+ }
+ });
}
private void addWithoutBarcode() {
@@ -364,19 +381,23 @@ public void addFromImage() {
PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_IMAGE);
}
- private void addFromImageAfterPermission() {
+ public void addFromPdfFile() {
+ PermissionUtils.requestStorageReadPermission(this, PERMISSION_SCAN_ADD_FROM_PDF);
+ }
+
+ private void addFromImageOrFileAfterPermission(String mimeType, ActivityResultLauncher launcher, int chooserText, int errorMessage) {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
- photoPickerIntent.setType("image/*");
+ photoPickerIntent.setType(mimeType);
Intent contentIntent = new Intent(Intent.ACTION_GET_CONTENT);
- contentIntent.setType("image/*");
+ contentIntent.setType(mimeType);
- Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(R.string.addFromImage));
+ Intent chooserIntent = Intent.createChooser(photoPickerIntent, getString(chooserText));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { contentIntent });
try {
- photoPickerLauncher.launch(chooserIntent);
+ launcher.launch(chooserIntent);
} catch (ActivityNotFoundException e) {
setScannerActive(true);
- Toast.makeText(getApplicationContext(), R.string.failedLaunchingPhotoPicker, Toast.LENGTH_LONG).show();
+ Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
Log.e(TAG, "No activity found to handle intent", e);
}
}
@@ -424,9 +445,13 @@ public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[]
if (requestCode == CaptureManager.getCameraPermissionReqCode()) {
showCameraPermissionMissingText(!granted);
- } else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
+ } else if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE || requestCode == PERMISSION_SCAN_ADD_FROM_PDF) {
if (granted) {
- addFromImageAfterPermission();
+ if (requestCode == PERMISSION_SCAN_ADD_FROM_IMAGE) {
+ addFromImageOrFileAfterPermission("image/*", photoPickerLauncher, R.string.addFromImage, R.string.failedLaunchingPhotoPicker);
+ } else {
+ addFromImageOrFileAfterPermission("application/pdf", pdfPickerLauncher, R.string.addFromPdfFile, R.string.failedLaunchingFileManager);
+ }
} else {
setScannerActive(true);
Toast.makeText(this, R.string.storageReadPermissionRequired, Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/protect/card_locker/Utils.java b/app/src/main/java/protect/card_locker/Utils.java
index 1fb6c16ea1..f3df29134b 100644
--- a/app/src/main/java/protect/card_locker/Utils.java
+++ b/app/src/main/java/protect/card_locker/Utils.java
@@ -12,8 +12,10 @@
import android.graphics.Color;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
+import android.graphics.pdf.PdfRenderer;
import android.net.Uri;
import android.os.Build;
+import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.text.Layout;
import android.text.Spanned;
@@ -39,6 +41,7 @@
import androidx.palette.graphics.Palette;
import com.google.android.material.color.DynamicColors;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
@@ -46,6 +49,8 @@
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.multi.GenericMultipleBarcodeReader;
+import com.google.zxing.multi.MultipleBarcodeReader;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -64,6 +69,7 @@
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -83,12 +89,13 @@ public class Utils {
public static final int SELECT_BARCODE_REQUEST = 2;
public static final int BARCODE_SCAN = 3;
public static final int BARCODE_IMPORT_FROM_IMAGE_FILE = 4;
- public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 5;
- public static final int CARD_IMAGE_FROM_CAMERA_BACK = 6;
- public static final int CARD_IMAGE_FROM_CAMERA_ICON = 7;
- public static final int CARD_IMAGE_FROM_FILE_FRONT = 8;
- public static final int CARD_IMAGE_FROM_FILE_BACK = 9;
- public static final int CARD_IMAGE_FROM_FILE_ICON = 10;
+ public static final int BARCODE_IMPORT_FROM_PDF_FILE = 5;
+ public static final int CARD_IMAGE_FROM_CAMERA_FRONT = 6;
+ public static final int CARD_IMAGE_FROM_CAMERA_BACK = 7;
+ public static final int CARD_IMAGE_FROM_CAMERA_ICON = 8;
+ public static final int CARD_IMAGE_FROM_FILE_FRONT = 9;
+ public static final int CARD_IMAGE_FROM_FILE_BACK = 10;
+ public static final int CARD_IMAGE_FROM_FILE_ICON = 11;
public static final String CARD_IMAGE_FILENAME_REGEX = "^(card_)(\\d+)(_(?:front|back|icon)\\.png)$";
@@ -131,6 +138,80 @@ static public boolean needsDarkForeground(Integer backgroundColor) {
return ColorUtils.calculateLuminance(backgroundColor) > LUMINANCE_MIDPOINT;
}
+ static public List retrieveBarcodesFromImage(Context context, Uri uri) {
+ Log.i(TAG, "Received image file with possible barcode");
+
+ if (uri == null) {
+ Log.e(TAG, "Uri did not contain any data");
+ Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
+ return new ArrayList<>();
+ }
+
+ Bitmap bitmap;
+ try {
+ bitmap = retrieveImageFromUri(context, uri);
+ } catch (IOException e) {
+ Log.e(TAG, "Error getting data from image file");
+ e.printStackTrace();
+ Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
+ return new ArrayList<>();
+ }
+
+ List barcodesFromBitmap = getBarcodesFromBitmap(bitmap);
+
+ if (barcodesFromBitmap.isEmpty()) {
+ Log.i(TAG, "No barcode found in image file");
+ Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
+ }
+
+ return barcodesFromBitmap;
+ }
+
+ static public List retrieveBarcodesFromPdf(Context context, Uri uri) {
+ Log.i(TAG, "Received PDF file with possible barcode");
+
+ if (uri == null) {
+ Log.e(TAG, "Uri did not contain any data");
+ Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
+ return new ArrayList<>();
+ }
+
+ ParcelFileDescriptor parcelFileDescriptor;
+ PdfRenderer renderer;
+ try {
+ parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
+ renderer = new PdfRenderer(parcelFileDescriptor);
+ } catch (IOException e) {
+ Log.e(TAG, "Could not read file in uri");
+ Toast.makeText(context, R.string.errorReadingFile, Toast.LENGTH_LONG).show();
+ return new ArrayList<>();
+ }
+
+ // Loop over all pages to find barcodes
+ List barcodesFromPdfPages = new ArrayList<>();
+ Bitmap renderedPage;
+ for (int i = 0; i < renderer.getPageCount(); i++) {
+ PdfRenderer.Page page = renderer.openPage(i);
+ renderedPage = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Bitmap.Config.ARGB_8888);
+ page.render(renderedPage, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
+ page.close();
+
+ List barcodesFromPage = getBarcodesFromBitmap(renderedPage);
+ for (BarcodeValues barcodeValues : barcodesFromPage) {
+ barcodeValues.setNote(String.format(context.getString(R.string.pageWithNumber), i+1));
+ barcodesFromPdfPages.add(barcodeValues);
+ }
+ }
+ renderer.close();
+
+ if (barcodesFromPdfPages.isEmpty()) {
+ Log.i(TAG, "No barcode found in pdf file");
+ Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
+ }
+
+ return barcodesFromPdfPages;
+ }
+
/**
* Returns the Barcode format and content based on the result of an activity.
* It shows toasts to notify the end-user as needed itself and will return an empty
@@ -142,45 +223,20 @@ static public boolean needsDarkForeground(Integer backgroundColor) {
* @param context
* @return BarcodeValues
*/
- static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
+ static public List parseSetBarcodeActivityResult(int requestCode, int resultCode, Intent intent, Context context) {
String contents;
String format;
if (resultCode != Activity.RESULT_OK) {
- return new BarcodeValues(null, null);
+ return new ArrayList<>();
}
if (requestCode == Utils.BARCODE_IMPORT_FROM_IMAGE_FILE) {
- Log.i(TAG, "Received image file with possible barcode");
-
- Uri data = intent.getData();
- if (data == null) {
- Log.e(TAG, "Intent did not contain any data");
- Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
- return new BarcodeValues(null, null);
- }
-
- Bitmap bitmap;
- try {
- bitmap = retrieveImageFromUri(context, data);
- } catch (IOException e) {
- Log.e(TAG, "Error getting data from image file");
- e.printStackTrace();
- Toast.makeText(context, R.string.errorReadingImage, Toast.LENGTH_LONG).show();
- return new BarcodeValues(null, null);
- }
-
- BarcodeValues barcodeFromBitmap = getBarcodeFromBitmap(bitmap);
-
- if (barcodeFromBitmap.isEmpty()) {
- Log.i(TAG, "No barcode found in image file");
- Toast.makeText(context, R.string.noBarcodeFound, Toast.LENGTH_LONG).show();
- }
-
- Log.i(TAG, "Read barcode id: " + barcodeFromBitmap.content());
- Log.i(TAG, "Read format: " + barcodeFromBitmap.format());
+ return retrieveBarcodesFromImage(context, intent.getData());
+ }
- return barcodeFromBitmap;
+ if (requestCode == Utils.BARCODE_IMPORT_FROM_PDF_FILE) {
+ return retrieveBarcodesFromPdf(context, intent.getData());
}
if (requestCode == Utils.BARCODE_SCAN || requestCode == Utils.SELECT_BARCODE_REQUEST) {
@@ -196,7 +252,7 @@ static public BarcodeValues parseSetBarcodeActivityResult(int requestCode, int r
Log.i(TAG, "Read barcode id: " + contents);
Log.i(TAG, "Read format: " + format);
- return new BarcodeValues(format, contents);
+ return Collections.singletonList(new BarcodeValues(format, contents));
}
throw new UnsupportedOperationException("Unknown request code for parseSetBarcodeActivityResult");
@@ -216,22 +272,22 @@ private static Bitmap getBitmapSdkLessThan29(Uri data, Context context) throws I
return MediaStore.Images.Media.getBitmap(context.getContentResolver(), data);
}
- static public BarcodeValues getBarcodeFromBitmap(Bitmap bitmap) {
+ static public List getBarcodesFromBitmap(Bitmap bitmap) {
// This function is vulnerable to OOM, so we try again with a smaller bitmap is we get OOM
for (int i = 0; i < 10; i++) {
try {
- return Utils.getBarcodeFromBitmapReal(bitmap);
+ return Utils.getBarcodesFromBitmapReal(bitmap);
} catch (OutOfMemoryError e) {
- Log.w(TAG, "Ran OOM in getBarcodeFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
+ Log.w(TAG, "Ran OOM in getBarcodesFromBitmap! Trying again with smaller picture! Retry " + i + " of 10.");
bitmap = Bitmap.createScaledBitmap(bitmap, (int) Math.round(0.75 * bitmap.getWidth()), (int) Math.round(0.75 * bitmap.getHeight()), false);
}
}
// Give up
- return new BarcodeValues(null, null);
+ return new ArrayList<>();
}
- static private BarcodeValues getBarcodeFromBitmapReal(Bitmap bitmap) {
+ static private List getBarcodesFromBitmapReal(Bitmap bitmap) {
// In order to decode it, the Bitmap must first be converted into a pixel array...
int[] intArray = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
@@ -240,15 +296,63 @@ static private BarcodeValues getBarcodeFromBitmapReal(Bitmap bitmap) {
LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(), intArray);
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
+ List barcodeValuesList = new ArrayList<>();
try {
- Result barcodeResult = new MultiFormatReader().decode(binaryBitmap);
+ MultiFormatReader multiFormatReader = new MultiFormatReader();
+ MultipleBarcodeReader multipleBarcodeReader = new GenericMultipleBarcodeReader(multiFormatReader);
+
+ Result[] barcodeResults = multipleBarcodeReader.decodeMultiple(binaryBitmap);
+
+ for (Result barcodeResult : barcodeResults) {
+ Log.i(TAG, "Read barcode id: " + barcodeResult.getText());
+ Log.i(TAG, "Read format: " + barcodeResult.getBarcodeFormat().name());
+
+ barcodeValuesList.add(new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText()));
+ }
- return new BarcodeValues(barcodeResult.getBarcodeFormat().name(), barcodeResult.getText());
+ return barcodeValuesList;
} catch (NotFoundException e) {
- return new BarcodeValues(null, null);
+ return barcodeValuesList;
}
}
+ static public void makeUserChooseBarcodeFromList(Context context, List barcodeValuesList, BarcodeValuesListDisambiguatorCallback callback) {
+ // If there is only one choice, consider it chosen
+ if (barcodeValuesList.size() == 1) {
+ callback.onUserChoseBarcode(barcodeValuesList.get(0));
+ return;
+ }
+
+ // Ask user to choose a barcode
+ // TODO: This should contain an image of the barcode in question to help users understand the choice they're making
+ CharSequence[] barcodeDescriptions = new CharSequence[barcodeValuesList.size()];
+ for (int i = 0; i < barcodeValuesList.size(); i++) {
+ BarcodeValues barcodeValues = barcodeValuesList.get(i);
+ CatimaBarcode catimaBarcode = CatimaBarcode.fromName(barcodeValues.format());
+
+ String barcodeContent = barcodeValues.content();
+ // Shorten overly long barcodes
+ if (barcodeContent.length() > 22) {
+ barcodeContent = barcodeContent.substring(0, 20) + "…";
+ }
+
+ if (barcodeValues.note() != null) {
+ barcodeDescriptions[i] = String.format("%s: %s (%s)", barcodeValues.note(), catimaBarcode.prettyName(), barcodeContent);
+ } else {
+ barcodeDescriptions[i] = String.format("%s (%s)", catimaBarcode.prettyName(), barcodeContent);
+ }
+ }
+
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
+ builder.setTitle(context.getString(R.string.multipleBarcodesFoundPleaseChooseOne));
+ builder.setItems(
+ barcodeDescriptions,
+ (dialogInterface, i) -> callback.onUserChoseBarcode(barcodeValuesList.get(i))
+ );
+ builder.setOnCancelListener(dialogInterface -> callback.onUserDismissedSelector());
+ builder.show();
+ }
+
static public Boolean isNotYetValid(Date validFromDate) {
// The note in `hasExpired` does not apply here, since the bug was fixed before this feature was added.
return validFromDate.after(getStartOfToday().getTime());
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b6e009e194..0c6473e256 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -341,4 +341,9 @@
Spend
Receive
Invalid amount
+ Select a PDF file
+ Could not read the file
+ Could not find a supported file manager
+ Which of the found barcodes do you want to use?
+ Page %d