diff --git a/app/build.gradle b/app/build.gradle
index 4dabccd8..28a2c6cf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
compileSdk 34
targetSdkVersion 34
multiDexEnabled true
- versionCode 42
- versionName "4.6.0"
+ versionCode 43
+ versionName "4.6.1"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
buildFeatures.dataBinding = true
vectorDrawables.useSupportLibrary = true
diff --git a/app/src/androidTest/java/com/secuso/privacyfriendlycodescanner/qrscanner/DatabaseMigrationInstrumentedTest.java b/app/src/androidTest/java/com/secuso/privacyfriendlycodescanner/qrscanner/DatabaseMigrationInstrumentedTest.java
index 4e50f38b..4bf63462 100644
--- a/app/src/androidTest/java/com/secuso/privacyfriendlycodescanner/qrscanner/DatabaseMigrationInstrumentedTest.java
+++ b/app/src/androidTest/java/com/secuso/privacyfriendlycodescanner/qrscanner/DatabaseMigrationInstrumentedTest.java
@@ -52,7 +52,7 @@ public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertEquals("com.secuso.privacyfriendlycodescanner.qrscanner", appContext.getPackageName());
+ assertEquals("com.secuso.privacyFriendlyCodeScanner", appContext.getPackageName());
}
@Before
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eb9c03df..471da6ee 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -193,4 +194,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/generator/QRGeneratorUtils.java b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/generator/QRGeneratorUtils.java
index 55cfc82f..05f2df13 100644
--- a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/generator/QRGeneratorUtils.java
+++ b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/generator/QRGeneratorUtils.java
@@ -1,10 +1,15 @@
package com.secuso.privacyfriendlycodescanner.qrscanner.generator;
+import static android.content.Context.WINDOW_SERVICE;
+
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
import android.graphics.Point;
import android.media.MediaScannerConnection;
import android.net.Uri;
@@ -15,14 +20,19 @@
import android.view.Display;
import android.view.WindowManager;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.google.zxing.qrcode.encoder.ByteMatrix;
+import com.google.zxing.qrcode.encoder.Encoder;
+import com.google.zxing.qrcode.encoder.QRCode;
import java.io.File;
import java.io.FileOutputStream;
@@ -31,9 +41,9 @@
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
+import java.util.HashMap;
import java.util.Locale;
-
-import static android.content.Context.WINDOW_SERVICE;
+import java.util.Map;
public class QRGeneratorUtils {
@@ -85,8 +95,7 @@ public static Uri getCachedUri() {
return cache;
}
- public static Uri createImage(Context context, String qrInputText, Contents.Type qrType, BarcodeFormat barcodeFormat, String errorCorrectionLevel) {
-
+ private static int getDimension(Context context) {
//Find screen size
WindowManager manager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
@@ -94,27 +103,107 @@ public static Uri createImage(Context context, String qrInputText, Contents.Type
display.getSize(point);
int width = point.x;
int height = point.y;
- int smallerDimension = width < height ? width : height;
- smallerDimension = smallerDimension * 3 / 4;
-
- //Encode with a QR Code image
- QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrInputText,
- null,
- qrType,
- barcodeFormat.toString(),
- smallerDimension);
- Bitmap bitmap_ = null;
- try {
- bitmap_ = qrCodeEncoder.encodeAsBitmap(errorCorrectionLevel);
- // return bitmap_;
+ int smallerDimension = Math.min(width, height);
+ return smallerDimension * 3 / 4;
+ }
- } catch (WriterException e) {
- e.printStackTrace();
+ public static Uri createImage(Context context, String qrInputText, Contents.Type qrType, BarcodeFormat barcodeFormat, String errorCorrectionLevel, boolean dots) {
+ int smallerDimension = getDimension(context);
+
+ Bitmap bitmap_ = null;
+ if (!dots) {
+ //Encode with a QR Code image
+ QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrInputText,
+ null,
+ qrType,
+ barcodeFormat.toString(),
+ smallerDimension);
+ try {
+ bitmap_ = qrCodeEncoder.encodeAsBitmap(errorCorrectionLevel);
+ } catch (WriterException e) {
+ e.printStackTrace();
+ }
+ } else {
+ final Map hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+ hints.put(EncodeHintType.MARGIN, 1);
+ QRCode code;
+ try {
+ code = Encoder.encode(qrInputText, ErrorCorrectionLevel.valueOf(errorCorrectionLevel), hints);
+ } catch (WriterException e) {
+ throw new RuntimeException(e);
+ }
+ bitmap_ = createDotQRCode(code, smallerDimension, smallerDimension, Color.BLACK, Color.WHITE, 1);
}
return cacheImage(context, bitmap_);
}
+ private static Bitmap createDotQRCode(QRCode code, int width, int height, @ColorInt int color, @ColorInt int backgroundColor, int quietZone) {
+ Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(image);
+ canvas.drawColor(backgroundColor);
+ Paint paint = new Paint();
+ paint.setColor(color);
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.FILL);
+
+
+ ByteMatrix input = code.getMatrix();
+ if (input == null) {
+ throw new IllegalArgumentException();
+ }
+
+ final int QR_WIDTH = input.getWidth() + (quietZone * 2);
+ final int QR_HEIGHT = input.getHeight() + (quietZone * 2);
+ final int OUTPUT_WIDTH = Math.max(width, QR_WIDTH);
+ final int OUTPUT_HEIGHT = Math.max(height, QR_HEIGHT);
+
+ final float SCALE = Math.min((float) OUTPUT_WIDTH / (float) QR_WIDTH, (float) OUTPUT_HEIGHT / (float) QR_HEIGHT); //scale from ByteMatrix to Canvas
+ final int POSITION_PATTERN_SIZE = 7; //size of the position pattern inside the ByteMatrix
+ final float POSITION_PATTERN_RADIUS = (SCALE * POSITION_PATTERN_SIZE) / 2f;
+ final float CIRCLE_RADIUS = (SCALE * 0.35f);
+ final float PADDING_LEFT = (OUTPUT_WIDTH - (input.getWidth() * SCALE)) / 2.0f + CIRCLE_RADIUS / 2.0f;
+ final float PADDING_TOP = (OUTPUT_HEIGHT - (input.getHeight() * SCALE)) / 2.0f + CIRCLE_RADIUS / 2.0f;
+
+ for (int y = 0; y < input.getHeight(); y++) {
+ for (int x = 0; x < input.getWidth(); x++) {
+ if (input.get(x, y) == 1) {
+ boolean isInPositionPatternArea = //do not draw anything inside the position pattern regions
+ x <= POSITION_PATTERN_SIZE && y <= POSITION_PATTERN_SIZE || //top left position pattern
+ x >= input.getWidth() - POSITION_PATTERN_SIZE && y <= POSITION_PATTERN_SIZE || //top right position pattern
+ x <= POSITION_PATTERN_SIZE && y >= input.getHeight() - POSITION_PATTERN_SIZE; //bottom left position pattern
+ if (!isInPositionPatternArea) {
+ float outputX = PADDING_LEFT + x * SCALE;
+ float outputY = PADDING_TOP + y * SCALE;
+ canvas.drawCircle(outputX + CIRCLE_RADIUS, outputY + CIRCLE_RADIUS, CIRCLE_RADIUS, paint);
+ }
+ }
+ }
+ }
+
+ //draw position patterns
+ drawPositionPattern(canvas, color, backgroundColor, PADDING_LEFT, PADDING_TOP, POSITION_PATTERN_RADIUS);
+ drawPositionPattern(canvas, color, backgroundColor, PADDING_LEFT + (input.getWidth() - POSITION_PATTERN_SIZE) * SCALE, PADDING_TOP, POSITION_PATTERN_RADIUS);
+ drawPositionPattern(canvas, color, backgroundColor, PADDING_LEFT, PADDING_TOP + (input.getHeight() - POSITION_PATTERN_SIZE) * SCALE, POSITION_PATTERN_RADIUS);
+
+ return image;
+ }
+
+ private static void drawPositionPattern(Canvas canvas, @ColorInt int color, @ColorInt int backgroundColor, float x, float y, float patternRadius) {
+ final float BACKGROUND_CIRCLE_RADIUS = patternRadius * 5f / 7f;
+ final float MIDDLE_DOT_RADIUS = patternRadius * 3f / 7f;
+
+ Paint paint = new Paint();
+ paint.setColor(color);
+ Paint bgPaint = new Paint();
+ bgPaint.setColor(backgroundColor);
+
+ canvas.drawCircle(x + patternRadius, y + patternRadius, patternRadius, paint);
+ canvas.drawCircle(x + patternRadius, y + patternRadius, BACKGROUND_CIRCLE_RADIUS, bgPaint);
+ canvas.drawCircle(x + patternRadius, y + patternRadius, MIDDLE_DOT_RADIUS, paint);
+ }
+
public static void saveCachedImageToExternalStorage(Context context) {
Bitmap bitmap = null;
try {
diff --git a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/helpers/Utils.java b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/helpers/Utils.java
index 91dcba47..2efa1e7d 100644
--- a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/helpers/Utils.java
+++ b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/helpers/Utils.java
@@ -79,7 +79,9 @@ public static Bitmap generateCode(String data, BarcodeFormat original_format, in
if (!hints.containsKey(ERROR_CORRECTION) && metadata != null && metadata.containsKey(ERROR_CORRECTION_LEVEL) && format.equals(original_format)) {
Object ec = metadata.get(ERROR_CORRECTION_LEVEL);
if (ec != null) {
- hints.put(ERROR_CORRECTION, ec);
+ String errorCorrection = ec.toString();
+ errorCorrection = errorCorrection.replace("%", ""); // Sometimes the error correction value contains a percent sign
+ hints.put(ERROR_CORRECTION, errorCorrection);
}
}
if (!hints.containsKey(ERROR_CORRECTION) && format != BarcodeFormat.AZTEC && format != BarcodeFormat.PDF_417) {
diff --git a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/activities/generator/QrGeneratorDisplayActivity.java b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/activities/generator/QrGeneratorDisplayActivity.java
index fec35c9d..b95d3d0f 100644
--- a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/activities/generator/QrGeneratorDisplayActivity.java
+++ b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/activities/generator/QrGeneratorDisplayActivity.java
@@ -54,14 +54,17 @@ public class QrGeneratorDisplayActivity extends AppCompatActivity {
String qrInputText = "";
Contents.Type qrInputType = Contents.Type.UNDEFINED;
+ private static final String BARCODE_FORMAT_QR_CODE_DOTS = BarcodeFormat.QR_CODE.name() + "_DOTS";
private String[] barcodeFormats = new String[]{
BarcodeFormat.QR_CODE.name(),
+ BARCODE_FORMAT_QR_CODE_DOTS,
BarcodeFormat.AZTEC.name(),
BarcodeFormat.DATA_MATRIX.name(),
BarcodeFormat.PDF_417.name(),
BarcodeFormat.CODE_128.name()};
private Integer[] barcodeFormatIcons = new Integer[]{
R.drawable.ic_baseline_qr_code_24dp,
+ R.drawable.ic_baseline_qr_code_dots_24dp,
R.drawable.ic_aztec_code_24dp,
R.drawable.ic_data_matrix_code_24dp,
R.drawable.ic_pdf_417_code_24dp,
@@ -137,7 +140,7 @@ private void initDropDownMenus() {
}
private void updateDropDownMenus() {
- barcodeFormat = BarcodeFormat.valueOf(barcodeFormatMenu.getText().toString());
+ UpdateBarcodeFormatFromMenuValue();
if (barcodeFormat.equals(BarcodeFormat.QR_CODE)) {
currentErrorCorrections = errorCorrectionsQR;
@@ -151,7 +154,7 @@ private void updateDropDownMenus() {
updateErrorCorrectionMenu();
//Update icon
ImageView barcodeFormatIcon = findViewById(R.id.iconImageView);
- Glide.with(this).load(AppCompatResources.getDrawable(this, barcodeFormatIcons[Arrays.asList(barcodeFormats).indexOf(barcodeFormat.name())])).into(barcodeFormatIcon);
+ Glide.with(this).load(AppCompatResources.getDrawable(this, barcodeFormatIcons[Arrays.asList(barcodeFormats).indexOf(barcodeFormatMenu.getText().toString())])).into(barcodeFormatIcon);
}
@@ -176,11 +179,15 @@ private void updateErrorCorrectionMenu() {
private void generateAndUpdateImage() {
ImageView myImage = findViewById(R.id.resultQRCodeImage);
- barcodeFormat = BarcodeFormat.valueOf(barcodeFormatMenu.getText().toString());
+ UpdateBarcodeFormatFromMenuValue();
String errorCorrectionLevel = errorCorrectionMenu.getText().toString();
try {
Log.d(getClass().getSimpleName(), "Creating image...");
- Glide.with(this).asBitmap().load(QRGeneratorUtils.createImage(this, qrInputText, qrInputType, barcodeFormat, errorCorrectionLevel)).into(new BitmapImageViewTarget(myImage));
+ if (barcodeFormatMenu.getText().toString().equals(BARCODE_FORMAT_QR_CODE_DOTS)) {
+ Glide.with(this).asBitmap().load(QRGeneratorUtils.createImage(this, qrInputText, qrInputType, barcodeFormat, errorCorrectionLevel, true)).into(new BitmapImageViewTarget(myImage));
+ } else {
+ Glide.with(this).asBitmap().load(QRGeneratorUtils.createImage(this, qrInputText, qrInputType, barcodeFormat, errorCorrectionLevel, false)).into(new BitmapImageViewTarget(myImage));
+ }
} catch (IllegalArgumentException e) {
Toast.makeText(this, R.string.code_generation_error, Toast.LENGTH_SHORT).show();
Log.d(getClass().getSimpleName(), "Error during code generation.", e);
@@ -263,6 +270,14 @@ protected void onDestroy() {
QRGeneratorUtils.purgeCacheFolder(this);
}
+ private void UpdateBarcodeFormatFromMenuValue() {
+ if (barcodeFormatMenu.getText().toString().equals(BARCODE_FORMAT_QR_CODE_DOTS)) {
+ barcodeFormat = BarcodeFormat.QR_CODE;
+ } else {
+ barcodeFormat = BarcodeFormat.valueOf(barcodeFormatMenu.getText().toString());
+ }
+ }
+
@Override
protected void onResume() {
super.onResume();
diff --git a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/ContactResultFragment.java b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/ContactResultFragment.java
index 89eec521..8fa58302 100644
--- a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/ContactResultFragment.java
+++ b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/ContactResultFragment.java
@@ -62,7 +62,7 @@ public void onProceedPressed(Context context) {
Intent contact = new Intent(ContactsContract.Intents.Insert.ACTION);
contact.setType(ContactsContract.RawContacts.CONTENT_TYPE);
- contact.putExtra(ContactsContract.Intents.Insert.NAME, result.getNames() != null ? result.getNames()[0] : null);
+ contact.putExtra(ContactsContract.Intents.Insert.NAME, result.getNames() != null && result.getNames().length > 0 ? result.getNames()[0] : null);
contact.putExtra(ContactsContract.Intents.Insert.COMPANY, result.getOrg());
contact.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, result.getTitle());
contact.putExtra(ContactsContract.Intents.Insert.NOTES, result.getNote());
diff --git a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/URLResultFragment.java b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/URLResultFragment.java
index 6c725c27..13ef8236 100644
--- a/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/URLResultFragment.java
+++ b/app/src/main/java/com/secuso/privacyfriendlycodescanner/qrscanner/ui/resultfragments/URLResultFragment.java
@@ -28,6 +28,7 @@
import java.util.regex.Pattern;
public class URLResultFragment extends ResultFragment {
+ private static final String VALID_RFC3986_PROTOCOL_SCHEME = "^[a-zA-Z][a-zA-Z0-9+.-]*:.*$";
URIParsedResult result;
@@ -95,23 +96,14 @@ public void onProceedPressed(Context context) {
if (!checked) {
Toast.makeText(context, R.string.conform_url, Toast.LENGTH_LONG).show();
} else {
- String caption;
- String qrurl3;
- final String lowercase_qrurl = qrurl.toLowerCase();
- if (!lowercase_qrurl.startsWith("http://") && !lowercase_qrurl.startsWith("https://")) {
- qrurl3 = "http://" + qrurl;
-
- Intent url = new Intent(Intent.ACTION_VIEW);/// !!!!
- url.setData(Uri.parse(qrurl3));
- caption = getResources().getStringArray(R.array.url_array)[0];
- startActivity(Intent.createChooser(url, caption));
- } else {
- Intent url = new Intent(Intent.ACTION_VIEW);/// !!!!
- url.setData(Uri.parse(qrurl).normalizeScheme());
- caption = getResources().getStringArray(R.array.url_array)[0];
- startActivity(Intent.createChooser(url, caption));
-
+ String urlForIntentData = qrurl;
+ if (!qrurl.matches(VALID_RFC3986_PROTOCOL_SCHEME)) {
+ urlForIntentData = "http://" + qrurl;
}
+ Intent url = new Intent(Intent.ACTION_VIEW);/// !!!!
+ url.setData(Uri.parse(urlForIntentData).normalizeScheme());
+ String caption = getResources().getStringArray(R.array.url_array)[0];
+ startActivity(Intent.createChooser(url, caption));
}
}
diff --git a/app/src/main/res/drawable/ic_baseline_qr_code_dots_24dp.xml b/app/src/main/res/drawable/ic_baseline_qr_code_dots_24dp.xml
new file mode 100644
index 00000000..f2443604
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_qr_code_dots_24dp.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+