diff --git a/app/build.gradle b/app/build.gradle
index 79dffa3c..960f4b77 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e8ce3a2e..ce16d47d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,8 +6,12 @@
-
-
+
+
+
diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java
index f7eb9336..4c42f094 100644
--- a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java
+++ b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java
@@ -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";
diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java
index 46aff94a..12b6a9c9 100644
--- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java
+++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java
@@ -745,6 +745,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 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);
diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java
new file mode 100644
index 00000000..d73dd441
--- /dev/null
+++ b/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java
@@ -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 _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) 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);
+ }
+}
diff --git a/app/src/main/res/drawable/ic_qr_code_full.xml b/app/src/main/res/drawable/ic_qr_code_full.xml
new file mode 100644
index 00000000..9b5e128f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_qr_code_full.xml
@@ -0,0 +1,8 @@
+
+-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_share_entry.xml b/app/src/main/res/layout/activity_share_entry.xml
new file mode 100644
index 00000000..bc3c7dc4
--- /dev/null
+++ b/app/src/main/res/layout/activity_share_entry.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_action_mode.xml b/app/src/main/res/menu/menu_action_mode.xml
index e34cf3f7..1f6599e2 100644
--- a/app/src/main/res/menu/menu_action_mode.xml
+++ b/app/src/main/res/menu/menu_action_mode.xml
@@ -22,4 +22,12 @@
android:icon="@drawable/ic_delete_white"
android:tint="?attr/iconColorPrimary"
app:showAsAction="always"/>
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 421b1f63..8cd4f826 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -209,6 +209,7 @@
Unknown account name
Aegis could not import %d tokens. These tokens will be skipped. Press \'details\' to see more information about the errors.
Unable to read and process QR code
+ Unable to generate QR code
Select picture
Select icon
Toggle checkboxes
@@ -259,4 +260,11 @@
There are no codes to be shown. Start adding entries by tapping the plus sign in the bottom right corner
No entries found
+ Done
+ Transfer entries
+ %d / %d entries
+ Next
+ Previous
+ Transfer entry
+ Scan this QR code with the authenticator app you would like to transfer this entry to