Merge pull request #428 from michaelschattgen/feature/qr-transfer

Add ability to transfer tokens with qr codes
This commit is contained in:
Alexander Bakker 2020-06-06 12:24:14 +02:00 committed by GitHub
commit cca8c9bdf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 293 additions and 3 deletions

View file

@ -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'

View file

@ -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" />

View file

@ -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";

View file

@ -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);

View file

@ -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);
}
}

View 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>

View 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>

View file

@ -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>

View file

@ -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>