diff --git a/app/build.gradle b/app/build.gradle
index 35efdbf..ac7e36b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
+ id 'com.google.protobuf'
}
android {
@@ -29,6 +30,32 @@ android {
buildFeatures {
viewBinding true
}
+
+ sourceSets {
+ main {
+ java {
+ srcDir "build/generated/source/proto/debug/javalite"
+ }
+ proto {
+ srcDir 'src/main/proto'
+ }
+ }
+ }
+}
+
+protobuf {
+ protoc {
+ artifact = 'com.google.protobuf:protoc:21.0-rc-1'
+ }
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ java {
+ option "lite"
+ }
+ }
+ }
+ }
}
dependencies {
@@ -43,7 +70,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "androidx.biometric:biometric:1.1.0"
- implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.4'
+ implementation 'com.cringe_studios:CringeAuthenticatorLibrary:1.5'
implementation 'com.google.mlkit:barcode-scanning:17.1.0'
implementation 'com.google.code.gson:gson:2.8.9'
@@ -56,4 +83,6 @@ dependencies {
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
-}
\ No newline at end of file
+
+ implementation 'com.google.protobuf:protobuf-javalite:3.20.1'
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84240f9..33fa8eb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -47,6 +47,7 @@
+
diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java
index 9d4ffce..8d25ada 100644
--- a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java
+++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java
@@ -30,11 +30,13 @@ import com.cringe_studios.cringe_authenticator.fragment.HomeFragment;
import com.cringe_studios.cringe_authenticator.fragment.MenuFragment;
import com.cringe_studios.cringe_authenticator.fragment.NamedFragment;
import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment;
+import com.cringe_studios.cringe_authenticator.model.OTPData;
import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract;
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
+import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
import com.cringe_studios.cringe_authenticator_library.OTPType;
import java.util.Locale;
@@ -58,12 +60,7 @@ public class MainActivity extends AppCompatActivity {
//getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); TODO: enable secure flag
- Integer themeID = SettingsUtil.THEMES.get(SettingsUtil.getTheme(this));
- if(themeID != null) {
- setTheme(themeID);
- }else {
- setTheme(R.style.Theme_CringeAuthenticator_Blue_Green);
- }
+ ThemeUtil.loadTheme(this);
setLocale(SettingsUtil.getLocale(this));
@@ -106,7 +103,7 @@ public class MainActivity extends AppCompatActivity {
Fragment fragment = NavigationUtil.getCurrentFragment(this);
if(fragment instanceof GroupFragment) {
GroupFragment frag = (GroupFragment) fragment;
- frag.addOTP(obj.getData());
+ for(OTPData d : obj.getData()) frag.addOTP(d);
}
});
}
@@ -238,7 +235,7 @@ public class MainActivity extends AppCompatActivity {
if(frag instanceof MenuFragment) {
((MenuFragment) frag).addGroup(group);
}
- });
+ }, null);
}
@Override
diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java
index 0051ec1..3bdd609 100644
--- a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java
+++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/MenuFragment.java
@@ -59,7 +59,7 @@ public class MenuFragment extends NamedFragment {
case 0:
DialogUtil.showCreateGroupDialog(getLayoutInflater(), SettingsUtil.getGroupName(requireContext(), group), newName -> {
renameGroup(group, newName);
- });
+ }, null);
break;
case 1:
diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java
index 37e2dec..04e1b2e 100644
--- a/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java
+++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/model/OTPData.java
@@ -1,7 +1,5 @@
package com.cringe_studios.cringe_authenticator.model;
-import androidx.annotation.NonNull;
-
import com.cringe_studios.cringe_authenticator_library.OTP;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPException;
@@ -13,6 +11,7 @@ import java.util.Objects;
public class OTPData implements Serializable {
private String name;
+ private String issuer;
private OTPType type;
private String secret;
private OTPAlgorithm algorithm;
@@ -24,8 +23,9 @@ public class OTPData implements Serializable {
// Cached
private transient OTP otp;
- public OTPData(String name, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, long counter, boolean checksum) {
+ public OTPData(String name, String issuer, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, long counter, boolean checksum) {
this.name = name;
+ this.issuer = issuer;
this.type = type;
this.secret = secret;
this.algorithm = algorithm;
@@ -39,6 +39,10 @@ public class OTPData implements Serializable {
return name;
}
+ public String getIssuer() {
+ return issuer;
+ }
+
public OTPType getType() {
return type;
}
@@ -98,31 +102,17 @@ public class OTPData implements Serializable {
}
}
- @NonNull
- @Override
- public String toString() {
- return "OTPData{" +
- "name='" + name + '\'' +
- ", type=" + type +
- ", secret='" + secret + '\'' +
- ", algorithm=" + algorithm +
- ", digits=" + digits +
- ", period=" + period +
- ", counter=" + counter +
- ", checksum=" + checksum +
- '}';
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OTPData otpData = (OTPData) o;
- return digits == otpData.digits && period == otpData.period && counter == otpData.counter && Objects.equals(name, otpData.name) && type == otpData.type && Objects.equals(secret, otpData.secret) && algorithm == otpData.algorithm;
+ return digits == otpData.digits && period == otpData.period && counter == otpData.counter && checksum == otpData.checksum && Objects.equals(name, otpData.name) && Objects.equals(issuer, otpData.issuer) && type == otpData.type && Objects.equals(secret, otpData.secret) && algorithm == otpData.algorithm && Objects.equals(otp, otpData.otp);
}
@Override
public int hashCode() {
- return Objects.hash(name, type, secret, algorithm, digits, period, counter);
+ return Objects.hash(name, issuer, type, secret, algorithm, digits, period, counter, checksum, otp);
}
+
}
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 e5ccfb4..154787e 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,6 +2,7 @@ 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;
@@ -27,9 +28,12 @@ import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
import androidx.camera.lifecycle.ProcessCameraProvider;
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.util.OTPParser;
+import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
+import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mlkit.vision.barcode.BarcodeScanner;
import com.google.mlkit.vision.barcode.BarcodeScanning;
@@ -48,12 +52,16 @@ public class QRScannerActivity extends AppCompatActivity {
private BarcodeScanner scanner;
+ private boolean process = true;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
+ ThemeUtil.loadTheme(this);
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.CAMERA}, 1234);
}
@@ -113,6 +121,8 @@ public class QRScannerActivity extends AppCompatActivity {
@OptIn(markerClass = ExperimentalGetImage.class)
@Override
public void analyze(@NonNull ImageProxy image) {
+ if(!process) return;
+
Image mediaImage = image.getImage();
if(mediaImage != null) {
InputImage input = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees());
@@ -132,18 +142,40 @@ public class QRScannerActivity extends AppCompatActivity {
if(code == null) return;
Uri uri = Uri.parse(code.getRawValue());
- try {
- success(OTPParser.parse(uri));
- }catch(IllegalArgumentException e) {
- error(e.getMessage());
- }
+ process = false;
+ importUri(uri);
}
}).addOnFailureListener(e -> {});
}
}
}
- private void success(@NonNull OTPData data) {
+ 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();
+ return;
+ }
+
+ try {
+ success(OTPParser.parse(uri));
+ }catch(IllegalArgumentException e) {
+ error(e.getMessage());
+ }
+ }
+
+ private void success(@NonNull OTPData... data) {
Intent result = new Intent();
result.putExtra("data", data);
setResult(Activity.RESULT_OK, result);
@@ -157,4 +189,9 @@ public class QRScannerActivity extends AppCompatActivity {
finish();
}
+ private void cancel() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
}
diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java
index f0ecf39..f73b342 100644
--- a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java
+++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerContract.java
@@ -28,7 +28,7 @@ public class QRScannerContract extends ActivityResultContract groups = SettingsUtil.getGroups(this);
- String[] groupNames = new String[groups.size()];
- for(int i = 0; i < groups.size(); i++) {
- groupNames[i] = SettingsUtil.getGroupName(this, groups.get(i));
- }
-
- // TODO: add option to create new group?
- AlertDialog dialog = new StyledDialogBuilder(this)
- .setTitle(R.string.uri_handler_add_code_title)
- .setItems(groupNames, (d, which) -> {
- SettingsUtil.addOTP(this, groups.get(which), data);
- Toast.makeText(this, R.string.uri_handler_code_added, Toast.LENGTH_SHORT).show();
- })
- .setPositiveButton(R.string.ok, (d, which) -> finish())
- .create();
-
- dialog.setOnDismissListener(d -> finish());
- dialog.show();
+ OTPData data = OTPParser.parse(uri);
+ importCodes(data);
}catch(IllegalArgumentException e) {
AlertDialog dialog = new StyledDialogBuilder(this)
.setTitle(R.string.uri_handler_failed_title)
@@ -60,4 +57,26 @@ public class URIHandlerActivity extends AppCompatActivity {
}
}
+ private void importMigration(Uri uri) {
+ try {
+ OTPData[] data = OTPParser.parseMigration(uri);
+ importCodes(data);
+ }catch(IllegalArgumentException e) {
+ AlertDialog dialog = new StyledDialogBuilder(this)
+ .setTitle(R.string.uri_handler_failed_title)
+ .setMessage(e.getMessage())
+ .setPositiveButton(R.string.ok, (d, which) -> finish())
+ .create();
+
+ dialog.setOnDismissListener(d -> finish());
+ dialog.show();
+ }
+ }
+
+ private void importCodes(OTPData... data) {
+ DialogUtil.showImportCodeDialog(this, group -> {
+ for(OTPData d : data) SettingsUtil.addOTP(this, group, d);
+ }, this::finish);
+ }
+
}
diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java
index 239affa..db4a495 100644
--- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java
+++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/DialogUtil.java
@@ -5,6 +5,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
@@ -19,6 +20,8 @@ import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPType;
import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
public class DialogUtil {
@@ -81,13 +84,23 @@ public class DialogUtil {
showCodeDialog(context, binding.getRoot(), () -> {
try {
String name = binding.inputName.getText().toString();
+ if(name.trim().isEmpty()) {
+ showErrorDialog(context, context.getString(R.string.otp_add_missing_name));
+ return false;
+ }
+
+ String issuer = binding.inputIssuer.getText().toString();
+ if(issuer.trim().isEmpty()) {
+ issuer = null;
+ }
+
String secret = binding.inputSecret.getText().toString();
OTPAlgorithm algorithm = (OTPAlgorithm) binding.inputAlgorithm.getSelectedItem();
int digits = (int) binding.inputDigits.getSelectedItem();
int period = Integer.parseInt(binding.inputPeriod.getText().toString());
boolean checksum = binding.inputChecksum.isChecked();
- OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0, checksum);
+ OTPData data = new OTPData(name, issuer, OTPType.TOTP, secret, algorithm, digits, period, 0, checksum);
String errorMessage = data.validate();
if(errorMessage != null) {
@@ -134,13 +147,23 @@ public class DialogUtil {
showCodeDialog(context, binding.getRoot(), () -> {
try {
String name = binding.inputName.getText().toString();
+ if(name.trim().isEmpty()) {
+ showErrorDialog(context, context.getString(R.string.otp_add_missing_name));
+ return false;
+ }
+
+ String issuer = binding.inputIssuer.getText().toString();
+ if(issuer.trim().isEmpty()) {
+ issuer = null;
+ }
+
String secret = binding.inputSecret.getText().toString();
OTPAlgorithm algorithm = (OTPAlgorithm) binding.inputAlgorithm.getSelectedItem();
int digits = (int) binding.inputDigits.getSelectedItem();
int counter = Integer.parseInt(binding.inputCounter.getText().toString());
boolean checksum = binding.inputChecksum.isChecked();
- OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, 0, counter, checksum);
+ OTPData data = new OTPData(name, issuer, OTPType.TOTP, secret, algorithm, digits, 0, counter, checksum);
String errorMessage = data.validate();
if(errorMessage != null) {
@@ -171,7 +194,7 @@ public class DialogUtil {
}
}
- public static void showCreateGroupDialog(LayoutInflater inflater, String initialName, Consumer callback) {
+ public static void showCreateGroupDialog(LayoutInflater inflater, String initialName, Consumer callback, Runnable onDismiss) {
Context context = inflater.getContext();
DialogCreateGroupBinding binding = DialogCreateGroupBinding.inflate(inflater);
@@ -181,7 +204,7 @@ public class DialogUtil {
.setTitle(R.string.action_new_group)
.setView(binding.getRoot())
.setPositiveButton(R.string.add, (view, which) -> {})
- .setNegativeButton(R.string.cancel, (view, which) -> {})
+ .setNegativeButton(R.string.cancel, (view, which) -> { if(onDismiss != null) onDismiss.run(); })
.create();
dialog.setOnShowListener(d -> {
@@ -194,9 +217,43 @@ public class DialogUtil {
dialog.dismiss();
callback.accept(binding.createGroupName.getText().toString());
+ if(onDismiss != null) onDismiss.run();
});
});
+ dialog.setOnCancelListener(d -> { if(onDismiss != null) onDismiss.run(); });
+ dialog.show();
+ }
+
+ public static void showImportCodeDialog(Context context, Consumer callback, Runnable onDismiss) {
+ List groups = SettingsUtil.getGroups(context);
+ String[] groupNames = new String[groups.size() + 1];
+
+ groupNames[0] = context.getString(R.string.uri_handler_create_group);
+ for(int i = 0; i < groups.size(); i++) {
+ groupNames[i + 1] = SettingsUtil.getGroupName(context, groups.get(i));
+ }
+
+ AlertDialog dialog = new StyledDialogBuilder(context)
+ .setTitle(R.string.uri_handler_add_code_title)
+ .setItems(groupNames, (d, which) -> {
+ if(which == 0) { // Create New Group
+ DialogUtil.showCreateGroupDialog(LayoutInflater.from(context), null, group -> {
+ String id = UUID.randomUUID().toString();
+ SettingsUtil.addGroup(context, id, group);
+ callback.accept(id);
+ }, onDismiss);
+ return;
+ }
+
+ callback.accept(groups.get(which - 1));
+ Toast.makeText(context, R.string.uri_handler_code_added, Toast.LENGTH_SHORT).show();
+ if(onDismiss != null) onDismiss.run();
+ })
+ .setNegativeButton(R.string.cancel, (d, which) -> { if(onDismiss != null) onDismiss.run(); })
+ .setOnCancelListener(d -> { if(onDismiss != null) onDismiss.run(); })
+ .create();
+
dialog.show();
}
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 8916394..355f759 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
@@ -1,20 +1,119 @@
package com.cringe_studios.cringe_authenticator.util;
import android.net.Uri;
+import android.util.Base64;
import com.cringe_studios.cringe_authenticator.model.OTPData;
+import com.cringe_studios.cringe_authenticator.proto.OTPMigration;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPType;
+import com.cringe_studios.cringe_authenticator_library.impl.Base32;
+import com.google.protobuf.InvalidProtocolBufferException;
public class OTPParser {
+ public static OTPData[] parseMigration(Uri uri) throws IllegalArgumentException {
+ if(!"otpauth-migration".equals(uri.getScheme())) {
+ throw new IllegalArgumentException("Wrong URI scheme");
+ }
+
+ if(!uri.isHierarchical()) {
+ throw new IllegalArgumentException("Not a hierarchical URI");
+ }
+
+ String data = uri.getQueryParameter("data");
+ if(data == null) {
+ throw new IllegalArgumentException("Missing data");
+ }
+
+ 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();
+
+ OTPType type;
+ switch(params.getType()) {
+ case OTP_TYPE_UNSPECIFIED:
+ case UNRECOGNIZED:
+ default:
+ // TODO: be more lenient and only exclude the broken codes
+ throw new IllegalArgumentException("Unknown OTP type in migration");
+ case OTP_TYPE_HOTP:
+ type = OTPType.HOTP;
+ break;
+ case OTP_TYPE_TOTP:
+ type = OTPType.TOTP;
+ break;
+ }
+
+ String secret = Base32.encode(params.getSecret().toByteArray());
+
+ OTPAlgorithm algorithm;
+ switch(params.getAlgorithm()) {
+ case ALGORITHM_UNSPECIFIED:
+ case UNRECOGNIZED:
+ default:
+ throw new IllegalArgumentException("Unknown or unsupported algorithm in migration");
+ case ALGORITHM_SHA1:
+ algorithm = OTPAlgorithm.SHA1;
+ break;
+ case ALGORITHM_SHA256:
+ algorithm = OTPAlgorithm.SHA256;
+ break;
+ case ALGORITHM_SHA512:
+ algorithm = OTPAlgorithm.SHA512;
+ break;
+ case ALGORITHM_MD5:
+ algorithm = OTPAlgorithm.MD5;
+ break;
+ }
+
+ int digits;
+ switch(params.getDigits()) {
+ case DIGIT_COUNT_UNSPECIFIED:
+ case UNRECOGNIZED:
+ default:
+ throw new IllegalArgumentException("Unknown or unsupported digit count in migration");
+ case DIGIT_COUNT_SIX:
+ digits = 6;
+ break;
+ case DIGIT_COUNT_EIGHT:
+ digits = 8;
+ break;
+ }
+
+ int period = 30; // Google authenticator doesn't support other periods
+ long counter = params.getCounter();
+ boolean checksum = false; // Google authenticator doesn't support checksums
+
+ otps[i] = new OTPData(name, issuer, type, secret, algorithm, digits, period, counter, checksum);
+ }
+ return otps;
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalArgumentException("Failed to parse migration data", e);
+ }
+ }
+
public static OTPData parse(Uri uri) throws IllegalArgumentException {
if(!"otpauth".equals(uri.getScheme())) {
throw new IllegalArgumentException("Wrong URI scheme");
}
+ if(!uri.isHierarchical()) {
+ throw new IllegalArgumentException("Not a hierarchical URI");
+ }
+
String type = uri.getHost();
String accountName = uri.getPath();
+ String issuer = uri.getQueryParameter("issuer");
String secret = uri.getQueryParameter("secret");
String algorithm = uri.getQueryParameter("algorithm");
String digits = uri.getQueryParameter("digits");
@@ -58,7 +157,7 @@ public class OTPParser {
}
}
- OTPData data = new OTPData(accountName, fType, secret, fAlgorithm, fDigits, fPeriod, fCounter, fChecksum);
+ OTPData data = new OTPData(accountName, issuer, fType, secret, fAlgorithm, fDigits, fPeriod, fCounter, fChecksum);
String errorMessage = data.validate();
if(errorMessage != null) {
diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java
new file mode 100644
index 0000000..dbc90fc
--- /dev/null
+++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java
@@ -0,0 +1,18 @@
+package com.cringe_studios.cringe_authenticator.util;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.cringe_studios.cringe_authenticator.R;
+
+public class ThemeUtil {
+
+ public static void loadTheme(AppCompatActivity activity) {
+ Integer themeID = SettingsUtil.THEMES.get(SettingsUtil.getTheme(activity));
+ if(themeID != null) {
+ activity.setTheme(themeID);
+ }else {
+ activity.setTheme(R.style.Theme_CringeAuthenticator_Blue_Green);
+ }
+ }
+
+}
diff --git a/app/src/main/res/layout/dialog_input_code_hotp.xml b/app/src/main/res/layout/dialog_input_code_hotp.xml
index 7ebf300..060cc21 100644
--- a/app/src/main/res/layout/dialog_input_code_hotp.xml
+++ b/app/src/main/res/layout/dialog_input_code_hotp.xml
@@ -14,6 +14,15 @@
android:hint="@string/otp_add_name"
android:autofillHints="" />
+
+
+
+
Wähle den Code-Typ
Neue Gruppe
Du musst einen Namen eingeben
- Scannen des Codes fehlgeschlagen: %s
+ Scannen fehlgeschlagen: %s
Abspielen des Videos fehlgeschlagen
OTP bearbeiten
Löschen?
@@ -38,6 +38,7 @@
Biometrische Authentifizierung aktivieren
Code hinzugefügt
Code hinzufügen
+ Neue Gruppe erstellen
Theme
Zähler
Prüfsumme hinzufügen
@@ -45,6 +46,10 @@
Name
Intervall
Hinzufügen des OTP-Codes fehlgeschlagen: %s
+ Aussteller (optional)
+ Name fehlt
+ OTP-Migration
+ Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren?
- Anzeigen
- Bearbeiten
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6de6646..faa29d7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -60,7 +60,7 @@
Select Code Type
New Group
You need to input a name
- Failed to scan code: %s
+ Scan failed: %s
Failed to play video
Edit OTP
Delete?
@@ -78,6 +78,7 @@
Require biometric unlock
Code added
Add Code
+ Create New Group
Theme
Counter
Add Checksum
@@ -85,6 +86,10 @@
Name
Period
Failed to update OTP: %s
+ Issuer (optional)
+ 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?
- View
- Edit
diff --git a/build.gradle b/build.gradle
index a100ce0..dea9549 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,4 +2,5 @@
plugins {
id 'com.android.application' version '8.0.2' apply false
id 'com.android.library' version '8.0.2' apply false
+ id 'com.google.protobuf' version '0.9.3' apply false
}
\ No newline at end of file