From 5af785804f9e5e5d706c7913ccea0b5e35a75b5f Mon Sep 17 00:00:00 2001 From: MrLetsplay Date: Wed, 16 Aug 2023 21:59:35 +0200 Subject: [PATCH] Add support for multi-code migrations --- app/src/main/AndroidManifest.xml | 3 +- .../model/OTPMigrationPart.java | 26 ++++++ .../scanner/QRScannerActivity.java | 90 ++++++++++++++----- .../urihandler/URIHandlerActivity.java | 3 +- .../cringe_authenticator/util/OTPParser.java | 11 ++- app/src/main/res/values-de-rDE/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPMigrationPart.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d2f052..82d44dd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,8 @@ + android:theme="@style/Theme.CringeAuthenticator.None" + android:configChanges="orientation|screenSize"> diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPMigrationPart.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPMigrationPart.java new file mode 100644 index 0000000..6cb8df0 --- /dev/null +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPMigrationPart.java @@ -0,0 +1,26 @@ +package com.cringe_studios.cringe_authenticator.model; + +public class OTPMigrationPart { + + private OTPData[] otps; + private int batchIndex; + private int batchSize; + + public OTPMigrationPart(OTPData[] otps, int batchIndex, int batchSize) { + this.otps = otps; + this.batchIndex = batchIndex; + this.batchSize = batchSize; + } + + public OTPData[] getOTPs() { + return otps; + } + + public int getBatchIndex() { + return batchIndex; + } + + public int getBatchSize() { + return batchSize; + } +} diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java index 154787e..b12cf9c 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java @@ -2,7 +2,6 @@ package com.cringe_studios.cringe_authenticator.scanner; import android.Manifest; import android.app.Activity; -import android.app.Dialog; import android.content.Intent; import android.content.pm.PackageManager; import android.media.Image; @@ -11,6 +10,7 @@ import android.os.Build; import android.os.Bundle; import android.view.MotionEvent; import android.view.WindowManager; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -31,6 +31,7 @@ import androidx.core.content.ContextCompat; import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding; import com.cringe_studios.cringe_authenticator.model.OTPData; +import com.cringe_studios.cringe_authenticator.model.OTPMigrationPart; import com.cringe_studios.cringe_authenticator.util.OTPParser; import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder; import com.cringe_studios.cringe_authenticator.util.ThemeUtil; @@ -40,6 +41,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning; import com.google.mlkit.vision.barcode.common.Barcode; import com.google.mlkit.vision.common.InputImage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.ExecutionException; public class QRScannerActivity extends AppCompatActivity { @@ -54,6 +58,12 @@ public class QRScannerActivity extends AppCompatActivity { private boolean process = true; + private List currentCodes; + + private OTPMigrationPart lastPart; + + private Toast t; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -69,6 +79,9 @@ public class QRScannerActivity extends AppCompatActivity { binding = ActivityQrScannerBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + currentCodes = new ArrayList<>(); + lastPart = null; + cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { @@ -81,11 +94,8 @@ public class QRScannerActivity extends AppCompatActivity { }, ContextCompat.getMainExecutor(this)); scanner = BarcodeScanning.getClient(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); + t = Toast.makeText(this, "0", Toast.LENGTH_LONG); + t.show(); } void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { @@ -121,7 +131,10 @@ public class QRScannerActivity extends AppCompatActivity { @OptIn(markerClass = ExperimentalGetImage.class) @Override public void analyze(@NonNull ImageProxy image) { - if(!process) return; + if(!process) { + image.close(); + return; + } Image mediaImage = image.getImage(); if(mediaImage != null) { @@ -152,19 +165,56 @@ public class QRScannerActivity extends AppCompatActivity { private void importUri(Uri uri) { if("otpauth-migration".equalsIgnoreCase(uri.getScheme())) { - Dialog dialog = new StyledDialogBuilder(this) - .setTitle(R.string.qr_scanner_migration_title) - .setMessage(R.string.qr_scanner_migration_message) - .setPositiveButton(R.string.yes, (d, which) -> { - try { - success(OTPParser.parseMigration(uri)); - }catch(IllegalArgumentException e) { - error(e.getMessage()); - } - }) - .setNegativeButton(R.string.no, (d, which) -> cancel()) - .setOnDismissListener(d -> cancel()) - .show(); + OTPMigrationPart part; + + try { + part = OTPParser.parseMigration(uri); + }catch(IllegalArgumentException e) { + error(e.getMessage()); + return; + } + + if((lastPart != null && part.getBatchIndex() != lastPart.getBatchIndex() + 1) || (lastPart == null && part.getBatchIndex() > 0)) { + // Not next batch, or first batch (if nothing was scanned yet), keep looking + process = true; + return; + } + + if(part.getBatchIndex() == 0) { + new StyledDialogBuilder(this) + .setTitle(R.string.qr_scanner_migration_title) + .setMessage(R.string.qr_scanner_migration_message) + .setPositiveButton(R.string.yes, (d, which) -> { + if(part.getBatchSize() == 1) { + success(part.getOTPs()); + }else { + currentCodes.addAll(Arrays.asList(part.getOTPs())); + lastPart = part; + Toast.makeText(this, getString(R.string.qr_scanner_migration_part, part.getBatchIndex()+ 1, part.getBatchSize()), Toast.LENGTH_LONG).show(); + process = true; + } + }) + .setNegativeButton(R.string.no, (d, which) -> cancel()) + .show() + .setCanceledOnTouchOutside(false); + }else { + currentCodes.addAll(Arrays.asList(part.getOTPs())); + Toast.makeText(this, getString(R.string.qr_scanner_migration_part, part.getBatchIndex()+ 1, part.getBatchSize()), Toast.LENGTH_LONG).show(); + + if(part.getBatchIndex() == part.getBatchSize() - 1) { + success(currentCodes.toArray(new OTPData[0])); + }else { + process = true; + } + } + + + return; + } + + if(lastPart != null) { + // Migration is being imported + process = true; return; } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java index 61adaec..27cbd70 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java @@ -43,7 +43,8 @@ public class URIHandlerActivity extends AppCompatActivity { importCodes(OTPParser.parse(uri)); break; case "otpauth-migration": - importCodes(OTPParser.parseMigration(uri)); + importCodes(OTPParser.parseMigration(uri).getOTPs()); + // TODO: notify user if there are multiple codes? break; } }catch(IllegalArgumentException e) { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java index 355f759..5bd562e 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/OTPParser.java @@ -4,6 +4,7 @@ import android.net.Uri; import android.util.Base64; import com.cringe_studios.cringe_authenticator.model.OTPData; +import com.cringe_studios.cringe_authenticator.model.OTPMigrationPart; import com.cringe_studios.cringe_authenticator.proto.OTPMigration; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPType; @@ -12,7 +13,7 @@ import com.google.protobuf.InvalidProtocolBufferException; public class OTPParser { - public static OTPData[] parseMigration(Uri uri) throws IllegalArgumentException { + public static OTPMigrationPart parseMigration(Uri uri) throws IllegalArgumentException { if(!"otpauth-migration".equals(uri.getScheme())) { throw new IllegalArgumentException("Wrong URI scheme"); } @@ -27,15 +28,16 @@ public class OTPParser { } byte[] dataBytes = Base64.decode(data, Base64.DEFAULT); + try { OTPMigration.MigrationPayload payload = OTPMigration.MigrationPayload.parseFrom(dataBytes); + int count = payload.getOtpParametersCount(); OTPData[] otps = new OTPData[count]; + for(int i = 0; i < payload.getOtpParametersCount(); i++) { OTPMigration.MigrationPayload.OtpParameters params = payload.getOtpParameters(i); - // TODO: issuer - String name = params.getName(); String issuer = params.getIssuer(); @@ -96,7 +98,8 @@ public class OTPParser { otps[i] = new OTPData(name, issuer, type, secret, algorithm, digits, period, counter, checksum); } - return otps; + + return new OTPMigrationPart(otps, payload.getBatchIndex(), payload.getBatchSize()); } catch (InvalidProtocolBufferException e) { throw new IllegalArgumentException("Failed to parse migration data", e); } diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 7a3c1ff..6044b1f 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -48,6 +48,7 @@ Name fehlt OTP-Migration Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren? + Code %d von %d gescannt Anzeigen Bearbeiten diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 625a79f..42ac5ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,7 @@ Missing name OTP Migration It seems like you\'re trying to import OTP codes from another app. Do you want to import all codes into this group? + Code %d of %d scanned View Edit