Add support for multi-code migrations
This commit is contained in:
parent
37a7eef5e1
commit
5af785804f
@ -38,7 +38,8 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".scanner.QRScannerActivity"
|
<activity android:name=".scanner.QRScannerActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.CringeAuthenticator.None">
|
android:theme="@style/Theme.CringeAuthenticator.None"
|
||||||
|
android:configChanges="orientation|screenSize">
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".urihandler.URIHandlerActivity"
|
<activity android:name=".urihandler.URIHandlerActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package com.cringe_studios.cringe_authenticator.scanner;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.media.Image;
|
import android.media.Image;
|
||||||
@ -11,6 +10,7 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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.R;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
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.OTPParser;
|
||||||
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
|
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
|
||||||
import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
|
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.barcode.common.Barcode;
|
||||||
import com.google.mlkit.vision.common.InputImage;
|
import com.google.mlkit.vision.common.InputImage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class QRScannerActivity extends AppCompatActivity {
|
public class QRScannerActivity extends AppCompatActivity {
|
||||||
@ -54,6 +58,12 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private boolean process = true;
|
private boolean process = true;
|
||||||
|
|
||||||
|
private List<OTPData> currentCodes;
|
||||||
|
|
||||||
|
private OTPMigrationPart lastPart;
|
||||||
|
|
||||||
|
private Toast t;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -69,6 +79,9 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
binding = ActivityQrScannerBinding.inflate(getLayoutInflater());
|
binding = ActivityQrScannerBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
currentCodes = new ArrayList<>();
|
||||||
|
lastPart = null;
|
||||||
|
|
||||||
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
||||||
cameraProviderFuture.addListener(() -> {
|
cameraProviderFuture.addListener(() -> {
|
||||||
try {
|
try {
|
||||||
@ -81,11 +94,8 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
}, ContextCompat.getMainExecutor(this));
|
}, ContextCompat.getMainExecutor(this));
|
||||||
|
|
||||||
scanner = BarcodeScanning.getClient();
|
scanner = BarcodeScanning.getClient();
|
||||||
}
|
t = Toast.makeText(this, "0", Toast.LENGTH_LONG);
|
||||||
|
t.show();
|
||||||
@Override
|
|
||||||
protected void onNewIntent(Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
|
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
|
||||||
@ -121,7 +131,10 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
@OptIn(markerClass = ExperimentalGetImage.class)
|
@OptIn(markerClass = ExperimentalGetImage.class)
|
||||||
@Override
|
@Override
|
||||||
public void analyze(@NonNull ImageProxy image) {
|
public void analyze(@NonNull ImageProxy image) {
|
||||||
if(!process) return;
|
if(!process) {
|
||||||
|
image.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Image mediaImage = image.getImage();
|
Image mediaImage = image.getImage();
|
||||||
if(mediaImage != null) {
|
if(mediaImage != null) {
|
||||||
@ -152,19 +165,56 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private void importUri(Uri uri) {
|
private void importUri(Uri uri) {
|
||||||
if("otpauth-migration".equalsIgnoreCase(uri.getScheme())) {
|
if("otpauth-migration".equalsIgnoreCase(uri.getScheme())) {
|
||||||
Dialog dialog = new StyledDialogBuilder(this)
|
OTPMigrationPart part;
|
||||||
.setTitle(R.string.qr_scanner_migration_title)
|
|
||||||
.setMessage(R.string.qr_scanner_migration_message)
|
try {
|
||||||
.setPositiveButton(R.string.yes, (d, which) -> {
|
part = OTPParser.parseMigration(uri);
|
||||||
try {
|
}catch(IllegalArgumentException e) {
|
||||||
success(OTPParser.parseMigration(uri));
|
error(e.getMessage());
|
||||||
}catch(IllegalArgumentException e) {
|
return;
|
||||||
error(e.getMessage());
|
}
|
||||||
}
|
|
||||||
})
|
if((lastPart != null && part.getBatchIndex() != lastPart.getBatchIndex() + 1) || (lastPart == null && part.getBatchIndex() > 0)) {
|
||||||
.setNegativeButton(R.string.no, (d, which) -> cancel())
|
// Not next batch, or first batch (if nothing was scanned yet), keep looking
|
||||||
.setOnDismissListener(d -> cancel())
|
process = true;
|
||||||
.show();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,8 @@ public class URIHandlerActivity extends AppCompatActivity {
|
|||||||
importCodes(OTPParser.parse(uri));
|
importCodes(OTPParser.parse(uri));
|
||||||
break;
|
break;
|
||||||
case "otpauth-migration":
|
case "otpauth-migration":
|
||||||
importCodes(OTPParser.parseMigration(uri));
|
importCodes(OTPParser.parseMigration(uri).getOTPs());
|
||||||
|
// TODO: notify user if there are multiple codes?
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}catch(IllegalArgumentException e) {
|
}catch(IllegalArgumentException e) {
|
||||||
|
@ -4,6 +4,7 @@ import android.net.Uri;
|
|||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
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.proto.OTPMigration;
|
||||||
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
|
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
|
||||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||||
@ -12,7 +13,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
|||||||
|
|
||||||
public class OTPParser {
|
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())) {
|
if(!"otpauth-migration".equals(uri.getScheme())) {
|
||||||
throw new IllegalArgumentException("Wrong URI scheme");
|
throw new IllegalArgumentException("Wrong URI scheme");
|
||||||
}
|
}
|
||||||
@ -27,15 +28,16 @@ public class OTPParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte[] dataBytes = Base64.decode(data, Base64.DEFAULT);
|
byte[] dataBytes = Base64.decode(data, Base64.DEFAULT);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OTPMigration.MigrationPayload payload = OTPMigration.MigrationPayload.parseFrom(dataBytes);
|
OTPMigration.MigrationPayload payload = OTPMigration.MigrationPayload.parseFrom(dataBytes);
|
||||||
|
|
||||||
int count = payload.getOtpParametersCount();
|
int count = payload.getOtpParametersCount();
|
||||||
OTPData[] otps = new OTPData[count];
|
OTPData[] otps = new OTPData[count];
|
||||||
|
|
||||||
for(int i = 0; i < payload.getOtpParametersCount(); i++) {
|
for(int i = 0; i < payload.getOtpParametersCount(); i++) {
|
||||||
OTPMigration.MigrationPayload.OtpParameters params = payload.getOtpParameters(i);
|
OTPMigration.MigrationPayload.OtpParameters params = payload.getOtpParameters(i);
|
||||||
|
|
||||||
// TODO: issuer
|
|
||||||
|
|
||||||
String name = params.getName();
|
String name = params.getName();
|
||||||
String issuer = params.getIssuer();
|
String issuer = params.getIssuer();
|
||||||
|
|
||||||
@ -96,7 +98,8 @@ public class OTPParser {
|
|||||||
|
|
||||||
otps[i] = new OTPData(name, issuer, type, secret, algorithm, digits, period, counter, checksum);
|
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) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
throw new IllegalArgumentException("Failed to parse migration data", e);
|
throw new IllegalArgumentException("Failed to parse migration data", e);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
<string name="otp_add_missing_name">Name fehlt</string>
|
<string name="otp_add_missing_name">Name fehlt</string>
|
||||||
<string name="qr_scanner_migration_title">OTP-Migration</string>
|
<string name="qr_scanner_migration_title">OTP-Migration</string>
|
||||||
<string name="qr_scanner_migration_message">Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren?</string>
|
<string name="qr_scanner_migration_message">Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren?</string>
|
||||||
|
<string name="qr_scanner_migration_part">Code %d von %d gescannt</string>
|
||||||
<string-array name="view_edit_move_delete">
|
<string-array name="view_edit_move_delete">
|
||||||
<item>Anzeigen</item>
|
<item>Anzeigen</item>
|
||||||
<item>Bearbeiten</item>
|
<item>Bearbeiten</item>
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
<string name="otp_add_missing_name">Missing name</string>
|
<string name="otp_add_missing_name">Missing name</string>
|
||||||
<string name="qr_scanner_migration_title">OTP Migration</string>
|
<string name="qr_scanner_migration_title">OTP Migration</string>
|
||||||
<string name="qr_scanner_migration_message">It seems like you\'re trying to import OTP codes from another app. Do you want to import all codes into this group?</string>
|
<string name="qr_scanner_migration_message">It seems like you\'re trying to import OTP codes from another app. Do you want to import all codes into this group?</string>
|
||||||
|
<string name="qr_scanner_migration_part">Code %d of %d scanned</string>
|
||||||
<string-array name="view_edit_move_delete">
|
<string-array name="view_edit_move_delete">
|
||||||
<item>View</item>
|
<item>View</item>
|
||||||
<item>Edit</item>
|
<item>Edit</item>
|
||||||
|
Loading…
Reference in New Issue
Block a user