Merge pull request #425 from Ullas-Aithal/feature/377
Added feature to scan qr code from image file #377
This commit is contained in:
commit
e1aa30967d
6 changed files with 145 additions and 10 deletions
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
9
app/src/main/res/drawable/ic_image_white.xml
Normal file
9
app/src/main/res/drawable/ic_image_white.xml
Normal 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>
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue