diff --git a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java index 9634143c..0643d53f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java +++ b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java @@ -12,9 +12,15 @@ import org.json.JSONException; import java.util.ArrayList; import java.util.Collections; + +import org.json.JSONObject; + import java.util.Date; import java.util.List; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.TimeUnit; public class Preferences { @@ -147,6 +153,56 @@ public class Preferences { _prefs.edit().putInt("pref_current_view_mode", viewMode.ordinal()).apply(); } + public Integer getUsageCount(UUID uuid) { + Integer usageCount = getUsageCounts().get(uuid); + + return usageCount != null ? usageCount : 0; + } + + public void resetUsageCount(UUID uuid) { + Map usageCounts = getUsageCounts(); + usageCounts.put(uuid, 0); + + setUsageCount(usageCounts); + } + + public void clearUsageCount() { + _prefs.edit().remove("pref_usage_count").apply(); + } + + public Map getUsageCounts() { + Map usageCounts = new HashMap<>(); + String usageCount = _prefs.getString("pref_usage_count", ""); + try { + JSONArray arr = new JSONArray(usageCount); + for(int i = 0; i < arr.length(); i++) { + JSONObject json = arr.getJSONObject(i); + usageCounts.put(UUID.fromString(json.getString("uuid")), json.getInt("count")); + } + + } catch (JSONException e) { + e.printStackTrace(); + } + + return usageCounts; + } + + public void setUsageCount(Map usageCounts) { + JSONArray usageCountJson = new JSONArray(); + for (Map.Entry entry : usageCounts.entrySet()) { + JSONObject entryJson = new JSONObject(); + try { + entryJson.put("uuid", entry.getKey()); + entryJson.put("count", entry.getValue()); + usageCountJson.put(entryJson); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + _prefs.edit().putString("pref_usage_count", usageCountJson.toString()).apply(); + } + public int getTimeout() { return _prefs.getInt("pref_timeout", -1); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/SortCategory.java b/app/src/main/java/com/beemdevelopment/aegis/SortCategory.java index bd56effb..5b3a9a90 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/SortCategory.java +++ b/app/src/main/java/com/beemdevelopment/aegis/SortCategory.java @@ -1,5 +1,6 @@ package com.beemdevelopment.aegis; +import com.beemdevelopment.aegis.helpers.comparators.UsageCountComparator; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator; import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator; @@ -12,7 +13,8 @@ public enum SortCategory { ACCOUNT, ACCOUNT_REVERSED, ISSUER, - ISSUER_REVERSED; + ISSUER_REVERSED, + USAGE_COUNT; private static SortCategory[] _values; @@ -40,6 +42,9 @@ public enum SortCategory { case ISSUER_REVERSED: comparator = Collections.reverseOrder(new IssuerNameComparator()); break; + case USAGE_COUNT: + comparator = Collections.reverseOrder(new UsageCountComparator()); + break; } return comparator; @@ -57,6 +62,8 @@ public enum SortCategory { return R.id.menu_sort_alphabetically; case ISSUER_REVERSED: return R.id.menu_sort_alphabetically_reverse; + case USAGE_COUNT: + return R.id.menu_sort_usage_count; default: return R.id.menu_sort_custom; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/helpers/comparators/UsageCountComparator.java b/app/src/main/java/com/beemdevelopment/aegis/helpers/comparators/UsageCountComparator.java new file mode 100644 index 00000000..d3f1ba2b --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/helpers/comparators/UsageCountComparator.java @@ -0,0 +1,12 @@ +package com.beemdevelopment.aegis.helpers.comparators; + +import com.beemdevelopment.aegis.vault.VaultEntry; + +import java.util.Comparator; + +public class UsageCountComparator implements Comparator { + @Override + public int compare(VaultEntry a, VaultEntry b) { + return Integer.compare(a.getUsageCount(), b.getUsageCount()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index 3e9fc3af..f5308881 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -22,6 +22,8 @@ import android.widget.AutoCompleteTextView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -100,6 +102,7 @@ public class EditEntryActivity extends AegisActivity { private TextInputEditText _textDigits; private TextInputLayout _textDigitsLayout; private TextInputEditText _textSecret; + private TextInputEditText _textUsageCount; private AutoCompleteTextView _dropdownType; private AutoCompleteTextView _dropdownAlgo; @@ -150,6 +153,7 @@ public class EditEntryActivity extends AegisActivity { _textDigits = findViewById(R.id.text_digits); _textDigitsLayout = findViewById(R.id.text_digits_layout); _textSecret = findViewById(R.id.text_secret); + _textUsageCount = findViewById(R.id.text_usage_count); _dropdownType = findViewById(R.id.dropdown_type); DropdownHelper.fillDropdown(this, _dropdownType, R.array.otp_types_array); _dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout); @@ -279,6 +283,8 @@ public class EditEntryActivity extends AegisActivity { } } }); + + _textUsageCount.setText(getPreferences().getUsageCount(entryUUID).toString()); } private void updateAdvancedFieldStatus(String otpType) { @@ -405,6 +411,14 @@ public class EditEntryActivity extends AegisActivity { case R.id.action_edit_icon: startIconSelection(); break; + case R.id.action_reset_usage_count: + Dialogs.showSecureDialog(new AlertDialog.Builder(this) + .setTitle(R.string.action_reset_usage_count) + .setMessage(R.string.action_reset_usage_count_dialog) + .setPositiveButton(android.R.string.yes, (dialog, which) -> resetUsageCount()) + .setNegativeButton(android.R.string.no, null) + .create()); + break; case R.id.action_default_icon: TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView); _iconView.setImageDrawable(drawable); @@ -431,6 +445,11 @@ public class EditEntryActivity extends AegisActivity { AegisActivity.Helper.startExtActivityForResult(this, chooserIntent, PICK_IMAGE_REQUEST); } + private void resetUsageCount() { + getPreferences().resetUsageCount(_origEntry.getUUID()); + _textUsageCount.setText("0"); + } + private void startIconSelection() { List iconPacks = getApp().getIconPackManager().getIconPacks().stream() .sorted(Comparator.comparing(IconPack::getName)) diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index 90d1fd1a..d8fb9e7e 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -57,6 +57,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @@ -165,6 +166,17 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene super.onDestroy(); } + @Override + protected void onPause() { + Map usageMap = _entryListView.getUsageCounts(); + if (usageMap != null) { + getPreferences().setUsageCount(usageMap); + } + + super.onPause(); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { _isAuthenticating = false; @@ -487,6 +499,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene // update the list of groups in the entry list view so that the chip gets updated _entryListView.setGroups(_vault.getGroups()); + // update the usage counts in case they are edited outside of the entrylistview + _entryListView.setUsageCounts(getPreferences().getUsageCounts()); + // refresh all codes to prevent showing old ones _entryListView.refresh(false); } else { @@ -605,6 +620,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene case R.id.menu_sort_alphabetically_name_reverse: sortCategory = SortCategory.ACCOUNT_REVERSED; break; + case R.id.menu_sort_usage_count: + sortCategory = SortCategory.USAGE_COUNT; + break; case R.id.menu_sort_custom: default: sortCategory = SortCategory.CUSTOM; @@ -625,6 +643,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private void loadEntries() { if (!_loaded) { + _entryListView.setUsageCounts(getPreferences().getUsageCounts()); _entryListView.addEntries(_vault.getEntries()); _entryListView.runEntriesAnimation(); _loaded = true; @@ -741,6 +760,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _entryListView.clearEntries(); _loaded = false; + if (userInitiated) { startAuthActivity(true); } else { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java index 4413b6d6..dde4edcf 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java @@ -21,6 +21,7 @@ import java.util.HashSet; public class AppearancePreferencesFragment extends PreferencesFragment { private Preference _groupsPreference; + private Preference _resetUsageCountPreference; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -36,6 +37,17 @@ public class AppearancePreferencesFragment extends PreferencesFragment { return true; }); + _resetUsageCountPreference = findPreference("pref_reset_usage_count"); + _resetUsageCountPreference.setOnPreferenceClickListener(preference -> { + Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) + .setTitle(R.string.preference_reset_usage_count) + .setMessage(R.string.preference_reset_usage_count_dialog) + .setPositiveButton(android.R.string.yes, (dialog, which) -> getPreferences().clearUsageCount()) + .setNegativeButton(android.R.string.no, null) + .create()); + return true; + }); + int currentTheme = prefs.getCurrentTheme().ordinal(); Preference darkModePreference = findPreference("pref_dark_mode"); darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.theme_titles)[currentTheme])); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java index 6a490c7d..8c49ace9 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryAdapter.java @@ -25,6 +25,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeSet; import java.util.UUID; public class EntryAdapter extends RecyclerView.Adapter implements ItemTouchHelperAdapter { @@ -32,6 +33,7 @@ public class EntryAdapter extends RecyclerView.Adapter implements I private List _entries; private List _shownEntries; private List _selectedEntries; + private Map _usageCounts; private VaultEntry _focusedEntry; private int _codeGroupSize; private boolean _showAccountName; @@ -139,6 +141,10 @@ public class EntryAdapter extends RecyclerView.Adapter implements I } public void addEntries(Collection entries) { + for (VaultEntry entry: entries) { + entry.setUsageCount(_usageCounts.containsKey(entry.getUUID()) ? _usageCounts.get(entry.getUUID()) : 0); + } + _entries.addAll(entries); updateShownEntries(); checkPeriodUniformity(true); @@ -282,6 +288,14 @@ public class EntryAdapter extends RecyclerView.Adapter implements I _viewMode = viewMode; } + public void setUsageCounts(Map usageCounts) { _usageCounts = usageCounts; } + + public Map getUsageCounts() { return _usageCounts; } + + public void setGroups(TreeSet groups) { + _view.setGroups(groups); + } + @Override public void onItemDismiss(int position) { @@ -363,6 +377,8 @@ public class EntryAdapter extends RecyclerView.Adapter implements I focusEntry(entry, _tapToRevealTime); } } + + incrementUsageCount(entry); } else { if (_selectedEntries.contains(entry)) { _view.onDeselect(entry); @@ -586,6 +602,15 @@ public class EntryAdapter extends RecyclerView.Adapter implements I updateDraggableStatus(); } + private void incrementUsageCount(VaultEntry entry) { + if (!_usageCounts.containsKey(entry.getUUID())) { + _usageCounts.put(entry.getUUID(), 1); + } else { + int usageCount = _usageCounts.get(entry.getUUID()); + _usageCounts.put(entry.getUUID(), ++usageCount); + } + } + public boolean isDragAndDropAllowed() { return _sortCategory == SortCategory.CUSTOM && _groupFilter.isEmpty() && _searchFilter == null; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java index 1db358c7..936d9a62 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java @@ -47,6 +47,7 @@ import com.google.android.material.chip.ChipGroup; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.TreeSet; import java.util.UUID; import java.util.stream.Collectors; @@ -184,6 +185,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { } } + public void setUsageCounts(Map usageCounts) { + _adapter.setUsageCounts(usageCounts); + } + + public Map getUsageCounts() { + return _adapter.getUsageCounts(); + } + public void setSearchFilter(String search) { _adapter.setSearchFilter(search); _touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed()); diff --git a/app/src/main/java/com/beemdevelopment/aegis/util/UUIDMap.java b/app/src/main/java/com/beemdevelopment/aegis/util/UUIDMap.java index 49b7abd6..c5b29f09 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/util/UUIDMap.java +++ b/app/src/main/java/com/beemdevelopment/aegis/util/UUIDMap.java @@ -141,6 +141,7 @@ public class UUIDMap implements Iterable, Serializa this(UUID.randomUUID()); } + @NonNull public final UUID getUUID() { return _uuid; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java index 49d42637..4d76f06f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java @@ -17,6 +17,8 @@ import java.util.Arrays; import java.util.Objects; import java.util.UUID; +import javax.annotation.Nonnull; + public class VaultEntry extends UUIDMap.Value { private String _name = ""; private String _issuer = ""; @@ -24,6 +26,7 @@ public class VaultEntry extends UUIDMap.Value { private OtpInfo _info; private byte[] _icon; private IconType _iconType = IconType.INVALID; + private int _usageCount; private VaultEntry(UUID uuid, OtpInfo info) { super(uuid); @@ -130,6 +133,10 @@ public class VaultEntry extends UUIDMap.Value { return _info; } + public int getUsageCount() { + return _usageCount; + } + public void setName(String name) { _name = name; } @@ -155,6 +162,8 @@ public class VaultEntry extends UUIDMap.Value { return _icon != null; } + public void setUsageCount(int usageCount) { _usageCount = usageCount; } + @Override public boolean equals(Object o) { if (!(o instanceof VaultEntry)) { diff --git a/app/src/main/res/drawable/ic_counter_black_24dp.xml b/app/src/main/res/drawable/ic_counter_black_24dp.xml new file mode 100644 index 00000000..30ac856e --- /dev/null +++ b/app/src/main/res/drawable/ic_counter_black_24dp.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit_entry.xml b/app/src/main/res/layout/activity_edit_entry.xml index fbaaa1ef..6c153591 100644 --- a/app/src/main/res/layout/activity_edit_entry.xml +++ b/app/src/main/res/layout/activity_edit_entry.xml @@ -61,6 +61,7 @@ android:layout_margin="15dp" android:src="@drawable/ic_check_black_24dp" app:tint="?attr/iconColorPrimary" /> + @@ -285,6 +286,42 @@ android:inputType="text"/> + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_edit.xml b/app/src/main/res/menu/menu_edit.xml index 5aae4b23..ac050268 100644 --- a/app/src/main/res/menu/menu_edit.xml +++ b/app/src/main/res/menu/menu_edit.xml @@ -11,6 +11,10 @@ android:id="@+id/action_edit_icon" android:title="@string/action_edit_icon" app:showAsAction="never"/> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e91b3705..a3674ae8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,11 +9,14 @@ Delete Transfer Edit icon + Reset usage count + Are you sure you want to set the usage count of this entry to 0? Restore default icon Discard Save Issuer Suggested + Usage count Preferences App @@ -245,6 +248,7 @@ Issuer (Z to A) Account (A to Z) Account (Z to A) + Usage count Custom New group… Enter a group name @@ -252,6 +256,9 @@ Group name Edit groups Manage and delete your groups here + Reset usage count + Reset the usage count of every entry in your vault + Are you sure you want to set the usage count of every entry in your vault to 0? Clear Highlight tokens when tapped diff --git a/app/src/main/res/xml/preferences_appearance.xml b/app/src/main/res/xml/preferences_appearance.xml index 6524e4d9..1c87755e 100644 --- a/app/src/main/res/xml/preferences_appearance.xml +++ b/app/src/main/res/xml/preferences_appearance.xml @@ -49,5 +49,11 @@ android:title="@string/preference_manage_groups" android:summary="@string/preference_manage_groups_summary" app:iconSpaceReserved="false"/> + +