Merge pull request #425 from Ullas-Aithal/feature/377

Added feature to scan qr code from image file #377
This commit is contained in:
Jakob Nixdorf 2020-02-02 12:47:02 +01:00 committed by GitHub
commit e1aa30967d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 10 deletions

View file

@ -23,16 +23,21 @@
package org.shadowice.flocke.andotp.Activities; package org.shadowice.flocke.andotp.Activities;
import android.Manifest;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -40,6 +45,7 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -62,6 +68,7 @@ import org.shadowice.flocke.andotp.Utilities.Constants;
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper; import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper; import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
import org.shadowice.flocke.andotp.Utilities.NotificationHelper; import org.shadowice.flocke.andotp.Utilities.NotificationHelper;
import org.shadowice.flocke.andotp.Utilities.ScanQRCodeFromFile;
import org.shadowice.flocke.andotp.Utilities.TokenCalculator; import org.shadowice.flocke.andotp.Utilities.TokenCalculator;
import org.shadowice.flocke.andotp.View.EntriesCardAdapter; import org.shadowice.flocke.andotp.View.EntriesCardAdapter;
import org.shadowice.flocke.andotp.View.ItemTouchHelper.SimpleItemTouchHelperCallback; import org.shadowice.flocke.andotp.View.ItemTouchHelper.SimpleItemTouchHelperCallback;
@ -75,6 +82,7 @@ import javax.crypto.SecretKey;
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod; 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.EncryptionType;
import static org.shadowice.flocke.andotp.Utilities.Constants.INTENT_QR_OPEN_IMAGE;
import static org.shadowice.flocke.andotp.Utilities.Constants.SortMode; import static org.shadowice.flocke.andotp.Utilities.Constants.SortMode;
public class MainActivity extends BaseActivity public class MainActivity extends BaseActivity
@ -212,6 +220,9 @@ public class MainActivity extends BaseActivity
case R.id.fabEnterDetails: case R.id.fabEnterDetails:
ManualEntryDialog.show(MainActivity.this, settings, adapter); ManualEntryDialog.show(MainActivity.this, settings, adapter);
return false; return false;
case R.id.fabScanQRFromImage:
openFileWithPermissions(INTENT_QR_OPEN_IMAGE);
return false;
default: default:
return false; return false;
} }
@ -406,15 +417,7 @@ public class MainActivity extends BaseActivity
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if(result != null) { if(result != null) {
if(result.getContents() != null) { if(result.getContents() != null) {
try { addQRCode(result.getContents());
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();
}
} }
} else if (requestCode == Constants.INTENT_MAIN_BACKUP && resultCode == RESULT_OK) { } else if (requestCode == Constants.INTENT_MAIN_BACKUP && resultCode == RESULT_OK) {
if (intent.getBooleanExtra("reload", false)) { if (intent.getBooleanExtra("reload", false)) {
@ -446,6 +449,10 @@ public class MainActivity extends BaseActivity
updateEncryption(authKey); updateEncryption(authKey);
} }
} else if (requestCode == INTENT_QR_OPEN_IMAGE && resultCode == RESULT_OK) {
if (intent != null) {
addQRCode(ScanQRCodeFromFile.scanQRImage(this, intent.getData()));
}
} }
} }
@ -741,4 +748,45 @@ public class MainActivity extends BaseActivity
tagsDrawerAdapter.setTags(tagsHashMap); tagsDrawerAdapter.setTags(tagsHashMap);
adapter.filterByTags(tagsDrawerAdapter.getActiveTags()); 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();
}
}
}
} }

View file

@ -143,4 +143,7 @@ public class Constants {
public final static String BACKUP_MIMETYPE_PLAIN = "application/json"; public final static String BACKUP_MIMETYPE_PLAIN = "application/json";
public final static String BACKUP_MIMETYPE_CRYPT = "binary/aes"; public final static String BACKUP_MIMETYPE_CRYPT = "binary/aes";
public final static String BACKUP_MIMETYPE_PGP = "application/pgp-encrypted"; 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;
} }

View file

@ -0,0 +1,67 @@
package org.shadowice.flocke.andotp.Utilities;
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;
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

View file

@ -4,7 +4,10 @@
android:id="@+id/fabEnterDetails" android:id="@+id/fabEnterDetails"
android:title="@string/button_enter_details" android:title="@string/button_enter_details"
android:icon="@drawable/ic_edit_white" /> android:icon="@drawable/ic_edit_white" />
<item
android:id="@+id/fabScanQRFromImage"
android:title="@string/button_qr_from_image"
android:icon="@drawable/ic_image_white" />
<item <item
android:id="@+id/fabScanQR" android:id="@+id/fabScanQR"
android:title="@string/button_scan_qr" android:title="@string/button_scan_qr"

View file

@ -10,6 +10,7 @@
<string name="button_settings">Settings</string> <string name="button_settings">Settings</string>
<string name="button_all_tags">All tags</string> <string name="button_all_tags">All tags</string>
<string name="button_no_tags">No tags</string> <string name="button_no_tags">No tags</string>
<string name="button_qr_from_image">QR code from image</string>
<!-- Custom formatting --> <!-- Custom formatting -->
<string name="format_custom_period">%d s</string> <string name="format_custom_period">%d s</string>
@ -64,6 +65,10 @@
<string name="toast_encryption_key_empty">Encryption key not loaded</string> <string name="toast_encryption_key_empty">Encryption key not loaded</string>
<string name="toast_intent_creation_failed">Invalid intent-provided code</string> <string name="toast_intent_creation_failed">Invalid intent-provided code</string>
<string name="toast_intent_creation_succeeded">Intent-provided code added</string> <string name="toast_intent_creation_succeeded">Intent-provided code added</string>
<string name="toast_file_load_error">Could not open the file</string>
<string name="toast_qr_error">Could not find/confirm QR code</string>
<string name="toast_qr_checksum_exception">Checksum verification failed while decoding QR code</string>
<string name="toast_qr_format_error">Format error in QR code</string>
<!-- Dialogs --> <!-- Dialogs -->
<string name="dialog_title_auth">Authenticate</string> <string name="dialog_title_auth">Authenticate</string>