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 d7310cc3..f09938a4 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 @@ -512,6 +512,9 @@ public class MainActivity extends BaseActivity } else if (mode == SortMode.LAST_USED) { sortMenu.setIcon(R.drawable.ic_sort_inverted_time_white); menu.findItem(R.id.menu_sort_last_used).setChecked(true); + } else if (mode == SortMode.MOST_USED) { + sortMenu.setIcon(R.drawable.ic_sort_inverted_time_white); + menu.findItem(R.id.menu_sort_most_used).setChecked(true); } } @@ -609,6 +612,16 @@ public class MainActivity extends BaseActivity } if (! settings.getLastUsedDialogShown()) showLastUsedDialog(); + } else if (id == R.id.menu_sort_most_used) { + item.setChecked(true); + sortMenu.setIcon(R.drawable.ic_sort_inverted_time_white); + saveSortMode(SortMode.MOST_USED); + if (adapter != null) { + adapter.setSortMode(SortMode.MOST_USED); + touchHelperCallback.setDragEnabled(false); + } + if (! settings.getMostUsedDialogShown()) + showMostUsedDialog(); } else if (tagsToggle.onOptionsItemSelected(item)) { return true; } @@ -630,6 +643,20 @@ public class MainActivity extends BaseActivity .create() .show(); } + private void showMostUsedDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dialog_title_manual_entry) + .setTitle(R.string.dialog_title_most_used) + .setMessage(R.string.dialog_msg_most_used) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + settings.setMostUsedDialogShown(true); + } + }) + .create() + .show(); + } private void setupDrawer() { tagsDrawerListView = findViewById(R.id.tags_list_in_drawer); 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 52c7d2ac..c0670461 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 @@ -60,6 +60,7 @@ public class Entry { private static final String JSON_TAGS = "tags"; private static final String JSON_THUMBNAIL = "thumbnail"; private static final String JSON_LAST_USED = "last_used"; + private static final String JSON_USED_FREQUENCY = "used_frequency"; private OTPType type = OTPType.TOTP; private int period = TokenCalculator.TOTP_DEFAULT_PERIOD; @@ -74,6 +75,7 @@ public class Entry { private Runnable hideTask = null; private long last_update = 0; private long last_used = 0; + private long used_frequency = 0; public List tags = new ArrayList<>(); private EntryThumbnail.EntryThumbnails thumbnail = EntryThumbnail.EntryThumbnails.Default; private static final int COLOR_DEFAULT = 0; @@ -240,6 +242,12 @@ public class Entry { } catch (Exception e) { this.last_used = 0; } + + try { + this.used_frequency = jsonObj.getLong(JSON_USED_FREQUENCY); + } catch (Exception e) { + this.used_frequency = 0; + } } public JSONObject toJSON() throws JSONException { @@ -252,6 +260,7 @@ public class Entry { jsonObj.put(JSON_ALGORITHM, algorithm.toString()); jsonObj.put(JSON_THUMBNAIL, getThumbnail().name()); jsonObj.put(JSON_LAST_USED, getLastUsed()); + jsonObj.put(JSON_USED_FREQUENCY, getUsedFrequency() ); if (type == OTPType.TOTP) jsonObj.put(JSON_PERIOD, getPeriod()); @@ -373,6 +382,14 @@ public class Entry { this.last_used = value; } + public long getUsedFrequency() { + return used_frequency; + } + + public void setUsedFrequency(long value) { + this.used_frequency = value; + } + public String getCurrentOTP() { return currentOTP; } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java index d12be4ec..d6598bef 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/Constants.java @@ -37,7 +37,7 @@ public class Constants { } public enum SortMode { - UNSORTED, LABEL, LAST_USED + UNSORTED, LABEL, LAST_USED, MOST_USED } public enum BackupType { 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 1fffef3d..3c92afc8 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 @@ -530,6 +530,13 @@ public class Settings { public void setLastUsedDialogShown(boolean value) { setBoolean(R.string.settings_key_last_used_dialog_shown, value); } + public boolean getMostUsedDialogShown() { + return getBoolean(R.string.settings_key_most_used_dialog_shown, false); + } + + public void setMostUsedDialogShown(boolean value) { + setBoolean(R.string.settings_key_most_used_dialog_shown, value); + } public boolean getNewBackupFormatDialogShown() { return getBoolean(R.string.settings_key_new_backup_format_dialog_shown, false); diff --git a/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java b/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java index 502f86c7..90c37422 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java @@ -270,7 +270,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter @Override public void onCopyButtonClicked(String text, int position) { copyToClipboard(text); - updateLastUsed(position, getRealIndex(position)); + updateLastUsedAndFrequency(position, getRealIndex(position)); if(context != null && settings.isMinimizeAppOnCopyEnabled()) { ((MainActivity)context).moveTaskToBack(true); } @@ -339,7 +339,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter entries.get(realIndex).setHideTask(null); } - boolean updateNeeded = updateLastUsed(pos, realIndex); + boolean updateNeeded = updateLastUsedAndFrequency(pos, realIndex); if (pos >= 0) { displayedEntries.get(pos).setVisible(false); @@ -390,19 +390,28 @@ public class EntriesCardAdapter extends RecyclerView.Adapter .show(); } - private boolean updateLastUsed(int position, int realIndex) { + private boolean updateLastUsedAndFrequency(int position, int realIndex) { long timeStamp = System.currentTimeMillis(); + long entryUsedFrequency = entries.get(realIndex).getUsedFrequency(); - if (position >= 0) + if (position >= 0) { + long displayEntryUsedFrequency = displayedEntries.get(position).getUsedFrequency(); displayedEntries.get(position).setLastUsed(timeStamp); + displayedEntries.get(position).setUsedFrequency(displayEntryUsedFrequency + 1); + } entries.get(realIndex).setLastUsed(timeStamp); + entries.get(realIndex).setUsedFrequency(entryUsedFrequency + 1); saveEntries(settings.getAutoBackupEncryptedFullEnabled()); if (sortMode == SortMode.LAST_USED) { displayedEntries = sortEntries(displayedEntries); notifyDataSetChanged(); return false; + } else if (sortMode == SortMode.MOST_USED) { + displayedEntries = sortEntries(displayedEntries); + notifyDataSetChanged(); + return false; } return true; @@ -728,6 +737,8 @@ public class EntriesCardAdapter extends RecyclerView.Adapter Collections.sort(sorted, new LabelComparator()); } else if (sortMode == SortMode.LAST_USED) { Collections.sort(sorted, new LastUsedComparator()); + } else if (sortMode == SortMode.MOST_USED) { + Collections.sort(sorted, new MostUsedComparator()); } return sorted; @@ -822,6 +833,13 @@ public class EntriesCardAdapter extends RecyclerView.Adapter } } + public class MostUsedComparator implements Comparator { + @Override + public int compare(Entry o1, Entry o2) { + return Long.compare(o2.getUsedFrequency(), o1.getUsedFrequency()); + } + } + public interface Callback { void onMoveEventStart(); void onMoveEventStop(); diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 3bc4f327..4e77a348 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -21,6 +21,9 @@ + diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 773706b4..6d79767a 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -60,6 +60,7 @@ pref_clear_keystore pref_last_used_dialog_shown + pref_most_used_dialog_shown 30 diff --git a/app/src/main/res/values/strings_main.xml b/app/src/main/res/values/strings_main.xml index ae98ae0c..a6ab39f2 100644 --- a/app/src/main/res/values/strings_main.xml +++ b/app/src/main/res/values/strings_main.xml @@ -49,6 +49,7 @@ Unsorted Label Last used + Most used Edit issuer Edit label @@ -77,6 +78,7 @@ Rename Counter Last used + Most used KeyStore error Enter password @@ -91,6 +93,10 @@ you have to have \"tap to reveal\" enabled or use the copy button.\n\nThis message will not be shown again. + In order for andOTP to recognize which token was used the most + you have to have \"tap to reveal\" enabled or use the copy button.\n\nThis message will not + be shown again. + Failed to load the encryption key from the KeyStore. Any entries that are added will get lost.\n\nTo still be able to use andOTP you can go to the Settings and switch the Database encryption to Password / PIN.