From 1e6896769db4d11e3d0600126f7dda3444891c2c Mon Sep 17 00:00:00 2001 From: Ullas-Aithal Date: Sun, 3 Nov 2019 14:20:24 -0600 Subject: [PATCH] Added feature to scan qr code from image file #377 --- .../andotp/Activities/MainActivity.java | 65 +++++++++++++++--- .../andotp/Activities/ScanQRCodeFromFile.java | 67 +++++++++++++++++++ .../flocke/andotp/Utilities/Constants.java | 3 + app/src/main/res/drawable/ic_image_white.xml | 9 +++ app/src/main/res/menu/menu_fab.xml | 5 +- app/src/main/res/values/strings_main.xml | 5 ++ 6 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/org/shadowice/flocke/andotp/Activities/ScanQRCodeFromFile.java create mode 100644 app/src/main/res/drawable/ic_image_white.xml diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java index 8c391422..f69610f1 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java @@ -23,16 +23,21 @@ package org.shadowice.flocke.andotp.Activities; +import android.Manifest; import android.animation.ObjectAnimator; import android.app.AlertDialog; import android.app.KeyguardManager; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.widget.LinearLayoutManager; @@ -40,6 +45,7 @@ import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.support.v7.widget.helper.ItemTouchHelper; +import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -75,6 +81,7 @@ import javax.crypto.SecretKey; import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod; import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType; +import static org.shadowice.flocke.andotp.Utilities.Constants.INTENT_QR_OPEN_IMAGE; import static org.shadowice.flocke.andotp.Utilities.Constants.SortMode; public class MainActivity extends BaseActivity @@ -212,6 +219,9 @@ public class MainActivity extends BaseActivity case R.id.fabEnterDetails: ManualEntryDialog.show(MainActivity.this, settings, adapter); return false; + case R.id.fabScanQRFromImage: + openFileWithPermissions(INTENT_QR_OPEN_IMAGE); + return false; default: return false; } @@ -406,15 +416,7 @@ public class MainActivity extends BaseActivity IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); if(result != null) { if(result.getContents() != null) { - try { - Entry e = new Entry(result.getContents()); - e.updateOTP(); - e.setLastUsed(System.currentTimeMillis()); - adapter.addEntry(e); - refreshTags(); - } catch (Exception e) { - Toast.makeText(this, R.string.toast_invalid_qr_code, Toast.LENGTH_LONG).show(); - } + addQRCode(result.getContents()); } } else if (requestCode == Constants.INTENT_MAIN_BACKUP && resultCode == RESULT_OK) { if (intent.getBooleanExtra("reload", false)) { @@ -446,6 +448,10 @@ public class MainActivity extends BaseActivity updateEncryption(authKey); } + } else if (requestCode == INTENT_QR_OPEN_IMAGE && resultCode == RESULT_OK) { + if (intent != null) { + addQRCode(ScanQRCodeFromFile.scanQRImage(this, intent.getData())); + } } } @@ -741,4 +747,45 @@ public class MainActivity extends BaseActivity tagsDrawerAdapter.setTags(tagsHashMap); adapter.filterByTags(tagsDrawerAdapter.getActiveTags()); } + + private void openFileWithPermissions(int intentId){ + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + showOpenFileSelector(intentId); + } else { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, Constants.PERMISSIONS_QR_READ_IMAGE); + } + } + + private void showOpenFileSelector(int intentId){ + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + startActivityForResult(intent, intentId); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == Constants.PERMISSIONS_QR_READ_IMAGE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + showOpenFileSelector(INTENT_QR_OPEN_IMAGE); + } else { + Toast.makeText(this, R.string.backup_toast_storage_permissions, Toast.LENGTH_LONG).show(); + } + } else { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + private void addQRCode(String result){ + if(!TextUtils.isEmpty(result)) { + try { + Entry e = new Entry(result); + e.updateOTP(); + e.setLastUsed(System.currentTimeMillis()); + adapter.addEntry(e); + refreshTags(); + } catch (Exception e) { + Toast.makeText(this, R.string.toast_invalid_qr_code, Toast.LENGTH_LONG).show(); + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/ScanQRCodeFromFile.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/ScanQRCodeFromFile.java new file mode 100644 index 00000000..610bb5e5 --- /dev/null +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/ScanQRCodeFromFile.java @@ -0,0 +1,67 @@ +package org.shadowice.flocke.andotp.Activities; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.widget.Toast; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.FormatException; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.RGBLuminanceSource; +import com.google.zxing.Reader; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; + +import org.shadowice.flocke.andotp.R; +import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper; +import org.shadowice.flocke.andotp.Utilities.Tools; +import java.io.IOException; + +public class ScanQRCodeFromFile { + public static String scanQRImage(Context context, Uri uri) { + //Check if external storage is accessible + if (!Tools.isExternalStorageReadable()) { + Toast.makeText(context, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); + return null; + } + //Get image in bytes + byte[] imageInBytes; + try { + imageInBytes = StorageAccessHelper.loadFile(context,uri); + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(context,R.string.toast_file_load_error,Toast.LENGTH_LONG).show(); + return null; + } + + Bitmap bMap = BitmapFactory.decodeByteArray(imageInBytes,0,imageInBytes.length); + String contents = null; + int[] intArray = new int[bMap.getWidth()*bMap.getHeight()]; + + bMap.getPixels(intArray, 0, bMap.getWidth(), 0, 0, bMap.getWidth(), bMap.getHeight()); + LuminanceSource source = new RGBLuminanceSource(bMap.getWidth(), bMap.getHeight(), intArray); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + Reader reader = new MultiFormatReader(); + //Try finding QR code + try { + Result result = reader.decode(bitmap); + contents = result.getText(); + } catch (NotFoundException e) { + e.printStackTrace(); + Toast.makeText(context,R.string.toast_qr_error,Toast.LENGTH_LONG).show(); + } catch (ChecksumException e) { + e.printStackTrace(); + Toast.makeText(context,R.string.toast_qr_checksum_exception,Toast.LENGTH_LONG).show(); + } catch (FormatException e) { + e.printStackTrace(); + Toast.makeText(context,R.string.toast_qr_format_error,Toast.LENGTH_LONG).show(); + } + //Return QR code (if found) + return contents; + } +} diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java index 7db63acd..bd24b22e 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java @@ -139,4 +139,7 @@ public class Constants { public final static String BACKUP_MIMETYPE_PLAIN = "application/json"; public final static String BACKUP_MIMETYPE_CRYPT = "binary/aes"; public final static String BACKUP_MIMETYPE_PGP = "application/pgp-encrypted"; + + public final static int INTENT_QR_OPEN_IMAGE = 400; + public final static int PERMISSIONS_QR_READ_IMAGE = 401; } diff --git a/app/src/main/res/drawable/ic_image_white.xml b/app/src/main/res/drawable/ic_image_white.xml new file mode 100644 index 00000000..ece1ac28 --- /dev/null +++ b/app/src/main/res/drawable/ic_image_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/menu/menu_fab.xml b/app/src/main/res/menu/menu_fab.xml index 7512d0b0..35528b6b 100644 --- a/app/src/main/res/menu/menu_fab.xml +++ b/app/src/main/res/menu/menu_fab.xml @@ -4,7 +4,10 @@ android:id="@+id/fabEnterDetails" android:title="@string/button_enter_details" android:icon="@drawable/ic_edit_white" /> - + Settings All tags No tags + QR code from image %d s @@ -64,6 +65,10 @@ Encryption key not loaded Invalid intent-provided code Intent-provided code added + Could not open the file + Could not find/confirm QR code + Checksum verification failed while decoding QR code + Format error in QR code Authenticate