mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-26 16:56:09 +00:00
Merge pull request #428 from michaelschattgen/feature/qr-transfer
Add ability to transfer tokens with qr codes
This commit is contained in:
commit
cca8c9bdf7
9 changed files with 293 additions and 3 deletions
|
@ -103,6 +103,7 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation "androidx.biometric:biometric:1.0.1"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".AegisApplication"
|
||||
|
@ -18,6 +22,8 @@
|
|||
android:theme="@style/AppTheme.NoActionBar"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:theme">
|
||||
<activity android:name=".ui.TransferEntriesActivity"
|
||||
android:label="@string/title_activity_transfer" />
|
||||
<activity
|
||||
android:name=".ui.AboutActivity"
|
||||
android:label="@string/title_activity_about" />
|
||||
|
|
|
@ -8,10 +8,11 @@ import com.beemdevelopment.aegis.encoding.Base64;
|
|||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GoogleAuthInfo {
|
||||
public class GoogleAuthInfo implements Serializable {
|
||||
public static final String SCHEME = "otpauth";
|
||||
public static final String SCHEME_EXPORT = "otpauth-migration";
|
||||
|
||||
|
|
|
@ -754,6 +754,20 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_share_qr:
|
||||
Intent intent = new Intent(getBaseContext(), TransferEntriesActivity.class);
|
||||
ArrayList<GoogleAuthInfo> authInfos = new ArrayList<>();
|
||||
for (VaultEntry entry : _selectedEntries) {
|
||||
GoogleAuthInfo authInfo = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
|
||||
authInfos.add(authInfo);
|
||||
}
|
||||
|
||||
intent.putExtra("authInfos", authInfos);
|
||||
startActivity(intent);
|
||||
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_delete:
|
||||
Dialogs.showDeleteEntriesDialog(MainActivity.this, (d, which) -> {
|
||||
deleteEntries(_selectedEntries);
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TransferEntriesActivity extends AegisActivity {
|
||||
private List<GoogleAuthInfo> _authInfos;
|
||||
private ImageView _qrImage;
|
||||
private TextView _issuer;
|
||||
private TextView _accountName;
|
||||
private TextView _entriesCount;
|
||||
private Button _nextButton;
|
||||
private Button _previousButton;
|
||||
|
||||
private VaultManager _vault;
|
||||
private int _currentEntryCount = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_share_entry);
|
||||
_vault = getApp().getVaultManager();
|
||||
|
||||
_qrImage = findViewById(R.id.ivQrCode);
|
||||
_issuer = findViewById(R.id.tvIssuer);
|
||||
_accountName = findViewById(R.id.tvAccountName);
|
||||
_entriesCount = findViewById(R.id.tvEntriesCount);
|
||||
_nextButton = findViewById(R.id.btnNext);
|
||||
_previousButton = findViewById(R.id.btnPrevious);
|
||||
|
||||
if (getSupportActionBar() != null){
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
_authInfos = (ArrayList<GoogleAuthInfo>) intent.getSerializableExtra("authInfos");
|
||||
|
||||
int controlVisibility = _authInfos.size() != 1 ? View.VISIBLE : View.INVISIBLE;
|
||||
_entriesCount.setVisibility(controlVisibility);
|
||||
_nextButton.setVisibility(controlVisibility);
|
||||
|
||||
_nextButton.setOnClickListener(v -> {
|
||||
if (_currentEntryCount < _authInfos.size()) {
|
||||
_previousButton.setVisibility(View.VISIBLE);
|
||||
_currentEntryCount++;
|
||||
generateQR();
|
||||
|
||||
if (_currentEntryCount == _authInfos.size()) {
|
||||
_nextButton.setText(R.string.done);
|
||||
}
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
_previousButton.setOnClickListener(v -> {
|
||||
if (_currentEntryCount > 1 ) {
|
||||
_nextButton.setText(R.string.next);
|
||||
_currentEntryCount--;
|
||||
generateQR();
|
||||
|
||||
if (_currentEntryCount == 1) {
|
||||
_previousButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
generateQR();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void generateQR() {
|
||||
GoogleAuthInfo selectedEntry = _authInfos.get(_currentEntryCount - 1);
|
||||
_issuer.setText(selectedEntry.getIssuer());
|
||||
_accountName.setText(selectedEntry.getAccountName());
|
||||
_entriesCount.setText(String.format(getString(R.string.entries_count), _currentEntryCount, _authInfos.size()));
|
||||
|
||||
QRCodeWriter writer = new QRCodeWriter();
|
||||
BitMatrix bitMatrix = null;
|
||||
try {
|
||||
bitMatrix = writer.encode(selectedEntry.getUri().toString(), BarcodeFormat.QR_CODE, 512, 512);
|
||||
} catch (WriterException e) {
|
||||
Dialogs.showErrorDialog(this, R.string.unable_to_generate_qrcode, e);
|
||||
return;
|
||||
}
|
||||
|
||||
int width = bitMatrix.getWidth();
|
||||
int height = bitMatrix.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
for (int y = 0; y < height; y++) {
|
||||
int offset = y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
pixels[offset + x] = bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
_qrImage.setImageBitmap(bitmap);
|
||||
}
|
||||
}
|
8
app/src/main/res/drawable/ic_qr_code_full.xml
Normal file
8
app/src/main/res/drawable/ic_qr_code_full.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:top="7dp" android:left="7dp" android:right="7dp" android:bottom="7dp">
|
||||
<vector android:height="24dp" android:viewportHeight="3328" android:tint="#FFFFFF"
|
||||
android:viewportWidth="3333" android:width="24.036058dp">
|
||||
<path android:fillColor="#FF000000" android:fillType="evenOdd" android:pathData="M5,0h1211v1206L5,1206L5,0zM3025,3025h309v304h-309v-304zM2432,3025h309v290h-618v-592h299v-300h304v-598h309v294h294v304h-294v304h-603v299zM1515,2417h299v-304h-289v-304h289v-304h-294v304h-309v-304h304L1515,603h309v902h299v304h294v-304h309v304h-294v304h-309v598h-299v603h-309v-897zM3020,1505h309v304h-309v-304zM608,1505h309v304L608,1809v-304zM5,1505h309v304L5,1809v-304zM1515,0h309v304h-309L1515,0zM0,2118h1211v1206L0,3324L0,2118zM294,2411h623v620L294,3031v-620zM2118,0h1211v1206L2118,1206L2118,0zM2412,293h623v620h-623L2412,293zM299,293h623v620L299,913L299,293z"/>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
108
app/src/main/res/layout/activity_share_entry.xml
Normal file
108
app/src/main/res/layout/activity_share_entry.xml
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/cardBackground"
|
||||
tools:context="com.beemdevelopment.aegis.ui.TransferEntriesActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivQrCode"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="250dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.496"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.399" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTransfer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/transfer_entry"
|
||||
android:textColor="?attr/primaryText"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tvDescription"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDescription"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:text="@string/transfer_entry_description"
|
||||
app:layout_constraintBottom_toTopOf="@+id/ivQrCode"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvIssuer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:text="Issuer"
|
||||
android:textColor="?attr/primaryText"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ivQrCode" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAccountName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:text="Accountname"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvIssuer" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNext"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:text="@string/next"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/code_primary_text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPrevious"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:text="@string/previous"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="invisible"
|
||||
android:textColor="@color/code_primary_text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvEntriesCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/entries_count"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/btnNext"
|
||||
app:layout_constraintHorizontal_bias="0.506"
|
||||
app:layout_constraintStart_toStartOf="@+id/btnPrevious" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -22,4 +22,12 @@
|
|||
android:icon="@drawable/ic_delete_white"
|
||||
android:tint="?attr/iconColorPrimary"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share_qr"
|
||||
android:title="@string/action_delete"
|
||||
android:orderInCategory="100"
|
||||
android:icon="@drawable/ic_qr_code_full"
|
||||
android:tint="?attr/iconColorPrimary"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
|
@ -209,6 +209,7 @@
|
|||
<string name="unknown_account_name">Unknown account name</string>
|
||||
<string name="import_error_dialog">Aegis could not import %d tokens. These tokens will be skipped. Press \'details\' to see more information about the errors.</string>
|
||||
<string name="unable_to_read_qrcode">Unable to read and process QR code</string>
|
||||
<string name="unable_to_generate_qrcode">Unable to generate QR code</string>
|
||||
<string name="select_picture">Select picture</string>
|
||||
<string name="select_icon">Select icon</string>
|
||||
<string name="toggle_checkboxes">Toggle checkboxes</string>
|
||||
|
@ -259,4 +260,11 @@
|
|||
</string>
|
||||
<string name="empty_list">There are no codes to be shown. Start adding entries by tapping the plus sign in the bottom right corner</string>
|
||||
<string name="empty_list_title">No entries found</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="title_activity_transfer">Transfer entries</string>
|
||||
<string name="entries_count">%d / %d entries</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="transfer_entry">Transfer entry</string>
|
||||
<string name="transfer_entry_description">Scan this QR code with the authenticator app you would like to transfer this entry to</string>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue