From 8107256cde89700fe3fc7356f9030482098b5c2b Mon Sep 17 00:00:00 2001 From: Jakob Nixdorf Date: Sun, 28 Mar 2021 20:56:22 +0200 Subject: [PATCH] New option to show the previous token Closes #506 --- .../andotp/Activities/MainActivity.java | 3 +- .../flocke/andotp/Database/Entry.java | 74 +++++++++++++------ .../flocke/andotp/Utilities/Settings.java | 4 + .../andotp/Utilities/TokenCalculator.java | 23 +++--- .../flocke/andotp/View/EntryViewHolder.java | 25 +++++-- .../res/layout/component_card_compact.xml | 47 +++++++----- .../res/layout/component_card_default.xml | 50 ++++++++----- .../main/res/layout/component_card_full.xml | 48 +++++++----- app/src/main/res/values/settings.xml | 1 + app/src/main/res/values/strings_settings.xml | 7 +- app/src/main/res/xml/preferences.xml | 6 ++ 11 files changed, 195 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java index b693a9cd..4c107ea3 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java @@ -491,7 +491,8 @@ public class MainActivity extends BaseActivity key.equals(getString(R.string.settings_key_theme_mode)) || key.equals(getString(R.string.settings_key_theme_black_auto)) || key.equals(getString(R.string.settings_key_hide_global_timeout)) || - key.equals(getString(R.string.settings_key_hide_issuer))) { + key.equals(getString(R.string.settings_key_hide_issuer)) || + key.equals(getString(R.string.settings_key_show_prev_token))) { recreateActivity = true; } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java b/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java index 63b52dc7..545b3c71 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java @@ -70,6 +70,7 @@ public class Entry { private String issuer; private String label; private String currentOTP; + private String prevOTP; private boolean visible = false; private Runnable hideTask = null; private long last_update = 0; @@ -150,7 +151,12 @@ public class Entry { String counter = uri.getQueryParameter("counter"); String issuer = uri.getQueryParameter("issuer"); - String label = getStrippedLabel(issuer, uri.getPath().substring(1)); + + String label = ""; + + if (uri.getPath() != null) + label = getStrippedLabel(issuer, uri.getPath().substring(1)); + String period = uri.getQueryParameter("period"); String digits = uri.getQueryParameter("digits"); String algorithm = uri.getQueryParameter("algorithm"); @@ -172,6 +178,10 @@ public class Entry { this.issuer = issuer; this.label = label; + + if (secret == null) + throw new Exception("Empty secret"); + if(type == OTPType.MOTP) { this.secret = secret.getBytes(); } else { @@ -475,6 +485,10 @@ public class Entry { return currentOTP; } + public String getPrevOTP() { + return prevOTP; + } + public String getPin() { return pin; } @@ -492,18 +506,48 @@ public class Entry { } public boolean updateOTP(boolean updateNow) { - if (type == OTPType.TOTP || type == OTPType.STEAM) { + if (type == OTPType.TOTP || type == OTPType.STEAM || type == OTPType.MOTP) { long time = System.currentTimeMillis() / 1000; long counter = time / this.getPeriod(); if (updateNow || counter > last_update) { - if (type == OTPType.TOTP) - currentOTP = TokenCalculator.TOTP_RFC6238(secret, period, digits, algorithm); - else if (type == OTPType.STEAM) - currentOTP = TokenCalculator.TOTP_Steam(secret, period, digits, algorithm); + // Store the previous token so we don't have to recalculate it every time + if (currentOTP != null && !currentOTP.isEmpty()) + prevOTP = currentOTP; + else + prevOTP = ""; + + switch (type) { + case TOTP: + currentOTP = TokenCalculator.TOTP_RFC6238(secret, period, digits, algorithm, 0); + + if (prevOTP == null || prevOTP.isEmpty()) + prevOTP = TokenCalculator.TOTP_RFC6238(secret, period, digits, algorithm, -1); + + break; + case STEAM: + currentOTP = TokenCalculator.TOTP_Steam(secret, period, digits, algorithm, 0); + + if (prevOTP == null || prevOTP.isEmpty()) + prevOTP = TokenCalculator.TOTP_Steam(secret, period, digits, algorithm, -1); + + break; + case MOTP: + String currentPin = this.getPin(); + + if (currentPin.isEmpty()) { + currentOTP = MOTP_NO_PIN_CODE; + } else { + currentOTP = TokenCalculator.MOTP(currentPin, new String(this.secret), time, 0); + + if (prevOTP == null || prevOTP.isEmpty()) + prevOTP = TokenCalculator.MOTP(currentPin, new String(this.secret), time, -1); + } + + break; + } last_update = counter; - //New OTP. Change color to default color setColor(COLOR_DEFAULT); return true; } else { @@ -512,22 +556,6 @@ public class Entry { } else if (type == OTPType.HOTP) { currentOTP = TokenCalculator.HOTP(secret, counter, digits, algorithm); return true; - } else if (type == OTPType.MOTP) { - long time = System.currentTimeMillis() / 1000; - long counter = time / this.getPeriod(); - if (counter > last_update || updateNow) { - String currentPin = this.getPin(); - if (currentPin.isEmpty()) { - currentOTP = MOTP_NO_PIN_CODE; - } else { - currentOTP = TokenCalculator.MOTP(currentPin, new String(this.secret), time); - } - last_update = counter; - setColor(COLOR_DEFAULT); - return true; - } else { - return false; - } } else { return false; } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Settings.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Settings.java index 4a6bcd83..0f41d53e 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Settings.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Settings.java @@ -675,4 +675,8 @@ public class Settings { String labelDisplay = getString(R.string.settings_key_label_display, R.string.settings_default_label_display); return Constants.LabelDisplay.valueOf(labelDisplay.toUpperCase(Locale.ENGLISH)); } + + public boolean getShowPrevToken() { + return getBoolean(R.string.settings_key_show_prev_token, false); + } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java index 7e157c29..96379560 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java @@ -61,19 +61,24 @@ public class TokenCalculator { return mac.doFinal(data); } + // TODO: Rewrite tests so this compatibility wrapper can be removed public static int TOTP_RFC6238(byte[] secret, int period, long time, int digits, HashAlgorithm algorithm) { - int fullToken = TOTP(secret, period, time, algorithm); + return TOTP_RFC6238(secret, period, time, digits, algorithm, 0); + } + + public static int TOTP_RFC6238(byte[] secret, int period, long time, int digits, HashAlgorithm algorithm, int offset) { + int fullToken = TOTP(secret, period, time, algorithm, offset); int div = (int) Math.pow(10, digits); return fullToken % div; } - public static String TOTP_RFC6238(byte[] secret, int period, int digits, HashAlgorithm algorithm) { - return Tools.formatTokenString(TOTP_RFC6238(secret, period, System.currentTimeMillis() / 1000, digits, algorithm), digits); + public static String TOTP_RFC6238(byte[] secret, int period, int digits, HashAlgorithm algorithm, int offset) { + return Tools.formatTokenString(TOTP_RFC6238(secret, period, System.currentTimeMillis() / 1000, digits, algorithm, offset), digits); } - public static String TOTP_Steam(byte[] secret, int period, int digits, HashAlgorithm algorithm) { - int fullToken = TOTP(secret, period, System.currentTimeMillis() / 1000, algorithm); + public static String TOTP_Steam(byte[] secret, int period, int digits, HashAlgorithm algorithm, int offset) { + int fullToken = TOTP(secret, period, System.currentTimeMillis() / 1000, algorithm, offset); StringBuilder tokenBuilder = new StringBuilder(); @@ -92,8 +97,8 @@ public class TokenCalculator { return Tools.formatTokenString(fullToken % div, digits); } - private static int TOTP(byte[] key, int period, long time, HashAlgorithm algorithm) { - return HOTP(key, time / period, algorithm); + private static int TOTP(byte[] key, int period, long time, HashAlgorithm algorithm, int offset) { + return HOTP(key, (time / period) + offset, algorithm); } private static int HOTP(byte[] key, long counter, HashAlgorithm algorithm) @@ -119,9 +124,9 @@ public class TokenCalculator { return r; } - public static String MOTP(String PIN, String secret, long epoch) + public static String MOTP(String PIN, String secret, long epoch, int offset) { - String epochText = String.valueOf(epoch / 10); + String epochText = String.valueOf((epoch / 10) + offset); String hashText = epochText + secret + PIN; String otp = ""; diff --git a/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java b/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java index ef578348..ec2a262f 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/View/EntryViewHolder.java @@ -69,11 +69,11 @@ public class EntryViewHolder extends RecyclerView.ViewHolder private final LinearLayout coverLayout; private final LinearLayout counterLayout; private final FrameLayout thumbnailFrame; - private final ImageView visibleImg; private final ImageView thumbnailImg; private final ImageButton menuButton; private final ImageButton copyButton; private final TextView value; + private final TextView valuePrev; private final TextView label; private final TextView counter; private final TextView tags; @@ -86,8 +86,8 @@ public class EntryViewHolder extends RecyclerView.ViewHolder card = v.findViewById(R.id.card_view); value = v.findViewById(R.id.valueText); + valuePrev = v.findViewById(R.id.valueTextPrev); valueLayout = v.findViewById(R.id.valueLayout); - visibleImg = v.findViewById(R.id.valueImg); thumbnailFrame = v.findViewById(R.id.thumbnailFrame); thumbnailImg = v.findViewById(R.id.thumbnailImg); coverLayout = v.findViewById(R.id.coverLayout); @@ -106,7 +106,6 @@ public class EntryViewHolder extends RecyclerView.ViewHolder menuButton.getDrawable().setColorFilter(colorFilter); copyButton.getDrawable().setColorFilter(colorFilter); - visibleImg.getDrawable().setColorFilter(colorFilter); invisibleImg.getDrawable().setColorFilter(colorFilter); setupOnClickListeners(menuButton, copyButton); @@ -221,6 +220,21 @@ public class EntryViewHolder extends RecyclerView.ViewHolder // save the unformatted token to the tag of this TextView for copy/paste value.setTag(entry.getCurrentOTP()); + if (settings.getShowPrevToken()) { + String tokenPrev = entry.getPrevOTP(); + + if (tokenPrev != null && !tokenPrev.isEmpty()) { + String tokenFormattedPrev = Tools.formatToken(tokenPrev, settings.getTokenSplitGroupSize()); + + valuePrev.setVisibility(View.VISIBLE); + valuePrev.setText(tokenFormattedPrev); + } else { + valuePrev.setVisibility(View.GONE); + } + } else { + valuePrev.setVisibility(View.GONE); + } + List entryTags = entry.getTags(); StringBuilder stringBuilder = new StringBuilder(); @@ -255,11 +269,9 @@ public class EntryViewHolder extends RecyclerView.ViewHolder if (entry.isVisible()) { valueLayout.setVisibility(View.VISIBLE); coverLayout.setVisibility(View.GONE); - visibleImg.setVisibility(View.GONE); } else { valueLayout.setVisibility(View.GONE); coverLayout.setVisibility(View.VISIBLE); - visibleImg.setVisibility(View.VISIBLE); } } } @@ -316,11 +328,9 @@ public class EntryViewHolder extends RecyclerView.ViewHolder if (enabled) { valueLayout.setVisibility(View.GONE); coverLayout.setVisibility(View.VISIBLE); - visibleImg.setVisibility(View.VISIBLE); } else { valueLayout.setVisibility(View.VISIBLE); coverLayout.setVisibility(View.GONE); - visibleImg.setVisibility(View.GONE); } } @@ -369,5 +379,6 @@ public class EntryViewHolder extends RecyclerView.ViewHolder } value.setTextColor(textColor); + valuePrev.setTextColor(textColor); } } diff --git a/app/src/main/res/layout/component_card_compact.xml b/app/src/main/res/layout/component_card_compact.xml index f10388b7..40ddb65f 100644 --- a/app/src/main/res/layout/component_card_compact.xml +++ b/app/src/main/res/layout/component_card_compact.xml @@ -1,13 +1,14 @@ - @@ -21,7 +22,8 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" - android:baselineAligned="false"> + android:baselineAligned="false" + tools:ignore="UselessParent"> + android:src="@mipmap/ic_launcher" + tools:ignore="ContentDescription" /> @@ -72,28 +75,31 @@ android:layout_height="wrap_content" android:visibility="gone"> - - + + + android:layout_height="wrap_content" + tools:ignore="UseCompoundDrawables"> + android:textSize="18sp" + tools:ignore="HardcodedText" /> + android:textSize="13.5sp" + tools:ignore="HardcodedText" /> @@ -146,12 +155,14 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/activity_horizontal_margin" android:visibility="gone" - android:orientation="horizontal" > + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables"> + app:srcCompat="@drawable/ic_alarm_gray" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/component_card_default.xml b/app/src/main/res/layout/component_card_default.xml index 9e5f56b0..940598e0 100644 --- a/app/src/main/res/layout/component_card_default.xml +++ b/app/src/main/res/layout/component_card_default.xml @@ -2,15 +2,17 @@ + style="?attr/cardStyle" > + android:baselineAligned="false" + tools:ignore="UselessParent"> + android:src="@mipmap/ic_launcher" + tools:ignore="ContentDescription" /> @@ -72,28 +76,33 @@ android:layout_height="wrap_content" android:visibility="gone"> - - + android:textStyle="bold" + android:textDirection="ltr" + tools:ignore="HardcodedText" /> + + + android:layout_height="wrap_content" + android:visibility="visible" + tools:ignore="UseCompoundDrawables"> + android:contentDescription="@string/label_hidden" + app:srcCompat="@drawable/ic_visibility_invisible" /> + android:textSize="18sp" + tools:ignore="HardcodedText" /> + android:textSize="13.5sp" + tools:ignore="HardcodedText" /> @@ -146,12 +158,14 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/activity_horizontal_margin" android:visibility="gone" - android:orientation="horizontal" > + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables"> + app:srcCompat="@drawable/ic_alarm_gray" + tools:ignore="ContentDescription" /> - @@ -21,7 +22,8 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" - android:baselineAligned="false"> + android:baselineAligned="false" + tools:ignore="UselessParent"> + android:background="?attr/thumbnailBackground" + tools:ignore="RtlSymmetry"> + android:src="@mipmap/ic_launcher" + tools:ignore="ContentDescription" /> @@ -69,13 +73,6 @@ android:layout_height="wrap_content" android:visibility="gone"> - - + + + android:layout_height="wrap_content" + tools:ignore="UseCompoundDrawables"> + android:text="Label" + tools:ignore="HardcodedText" /> + android:text="Tags" + tools:ignore="HardcodedText" /> @@ -143,12 +153,14 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/activity_horizontal_margin" android:visibility="gone" - android:orientation="horizontal" > + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables"> + app:srcCompat="@drawable/ic_alarm_gray" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 30518aa7..0170db29 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -29,6 +29,7 @@ perf_theme_mode pref_theme_black_auto pref_theme + pref_show_prev_token pref_label_size_sp pref_card_layout pref_label_scroll diff --git a/app/src/main/res/values/strings_settings.xml b/app/src/main/res/values/strings_settings.xml index 61d71ea7..44767cc2 100644 --- a/app/src/main/res/values/strings_settings.xml +++ b/app/src/main/res/values/strings_settings.xml @@ -1,5 +1,8 @@ - + + Settings @@ -28,6 +31,7 @@ Black theme Theme Card layout + Show previous token Label font size Label display Single-tap @@ -84,6 +88,7 @@ above 8.0 (Oreo) Use the black theme in dark mode + Show the previous token in addition to the current one App will be minimized when you copy the OTP to clipboard Specify which values should be included when searching Highlights token in red if it\'s expiring in 8 seconds diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 4f3f7069..742cf8c6 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -143,6 +143,12 @@ android:entryValues="@array/settings_values_tap" android:defaultValue="@string/settings_default_tap_double" /> + +