Skip to content

Add haptic feedback toggle for code refresh #1599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.VIBRATE" />

<!-- NOTE: Disabled for now. See issue: #1047
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/beemdevelopment/aegis/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ public boolean isEntryHighlightEnabled() {
return _prefs.getBoolean("pref_highlight_entry", false);
}

public boolean isHapticFeedbackEnabled() {
return _prefs.getBoolean("pref_haptic_feedback", true);
}

public boolean isPauseFocusedEnabled() {
boolean dependenciesEnabled = isTapToRevealEnabled() || isEntryHighlightEnabled();
if (!dependenciesEnabled) return false;
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/com/beemdevelopment/aegis/VibrationPatterns.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.beemdevelopment.aegis;

import java.util.Arrays;

public class VibrationPatterns {
public static final long[] EXPIRING = {475, 20, 5, 20, 965, 20, 5, 20, 965, 20, 5, 20, 420};
public static final long[] REFRESH_CODE = {0, 100};

public static long getLengthInMillis(long[] pattern) {
return Arrays.stream(pattern).sum();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.os.Handler;

import com.beemdevelopment.aegis.VibrationPatterns;

public class UiRefresher {
private boolean _running;
private Listener _listener;
Expand All @@ -23,14 +25,34 @@ public void start() {
}
_running = true;

_listener.onRefresh();
_handler.postDelayed(new Runnable() {
@Override
public void run() {
_listener.onRefresh();
_handler.postDelayed(this, _listener.getMillisTillNextRefresh());
}
}, _listener.getMillisTillNextRefresh());

_handler.postDelayed(new Runnable() {
@Override
public void run() {
_listener.onExpiring();
_handler.postDelayed(this, getNextRun());
}
}, getInitialRun());
}

private long getInitialRun() {
long sum = _listener.getMillisTillNextRefresh() - VibrationPatterns.getLengthInMillis(VibrationPatterns.EXPIRING);
if (sum < 0) {
return getNextRun();
}

return sum;
}

private long getNextRun() {
return (_listener.getMillisTillNextRefresh() + _listener.getPeriodMillis()) - VibrationPatterns.getLengthInMillis(VibrationPatterns.EXPIRING);
}

public void stop() {
Expand All @@ -40,6 +62,8 @@ public void stop() {

public interface Listener {
void onRefresh();
void onExpiring();
long getMillisTillNextRefresh();
long getPeriodMillis();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.beemdevelopment.aegis.helpers;

import android.content.Context;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;

import com.beemdevelopment.aegis.Preferences;

public class VibrationHelper {
private Preferences _preferences;

public VibrationHelper(Context context) {
_preferences = new Preferences(context);
}

public void vibratePattern(Context context, long[] pattern) {
if (!isHapticFeedbackEnabled()) {
return;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
VibratorManager vibratorManager = (VibratorManager) context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
if (vibratorManager != null) {
Vibrator vibrator = vibratorManager.getDefaultVibrator();
VibrationEffect effect = VibrationEffect.createWaveform(pattern, -1);
vibrator.vibrate(effect);
}
} else {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
VibrationEffect effect = VibrationEffect.createWaveform(pattern, -1);
vibrator.vibrate(effect);
}
}
}
}

public boolean isHapticFeedbackEnabled() {
return _preferences.isHapticFeedbackEnabled();
}
}
13 changes: 12 additions & 1 deletion app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,13 @@ private void handleIncomingIntent() {
}
}

@Override
protected void onStop() {
super.onStop();

_entryListView.onRefreshStop();
}

@Override
protected void onStart() {
super.onStart();
Expand Down Expand Up @@ -944,14 +951,18 @@ protected void onStart() {

// refresh all codes to prevent showing old ones
_entryListView.refresh(false);

_entryListView.onRefreshStart();
} else {
loadEntries();
checkTimeSyncSetting();
checkIconOptimization();

_entryListView.onRefreshStart();
}

_lockBackPressHandler.setEnabled(
_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)
_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)
);

handleIncomingIntent();
Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that the fix for the animation-related crash for entries that have a period of 7 is not included here. Do you want to create a separate PR for that?

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public class EntryHolder extends RecyclerView.ViewHolder {

private boolean _hidden;
private boolean _paused;

private TotpProgressBar _progressBar;
private MaterialCardView _view;

Expand Down Expand Up @@ -111,14 +110,22 @@ public void onRefresh() {
refreshCode();
}

@Override
public void onExpiring() { }

@Override
public long getMillisTillNextRefresh() {
return ((TotpInfo) _entry.getInfo()).getMillisTillNextRotation();
}

@Override
public long getPeriodMillis() {
return ((TotpInfo) _entry.getInfo()).getPeriod() * 1000L;
}
});
}

public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMode viewMode, AccountNamePosition accountNamePosition, boolean showIcon, boolean showProgress, boolean hidden, boolean paused, boolean dimmed, boolean showExpirationState, boolean showNextCode) {
public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMode viewMode, AccountNamePosition accountNamePosition, boolean showIcon, boolean nonUniform, boolean hidden, boolean paused, boolean dimmed, boolean showExpirationState, boolean showNextCode) {
_entry = entry;
_hidden = hidden;
_paused = paused;
Expand All @@ -140,7 +147,7 @@ public void setData(VaultEntry entry, Preferences.CodeGrouping groupSize, ViewMo
_favoriteIndicator.setVisibility(_entry.isFavorite() ? View.VISIBLE : View.INVISIBLE);

// only show the progress bar if there is no uniform period and the entry type is TotpInfo
setShowProgress(showProgress);
setShowProgress(nonUniform);

// only show the button if this entry is of type HotpInfo
_buttonRefresh.setVisibility(entry.getInfo() instanceof HotpInfo ? View.VISIBLE : View.GONE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.VibrationPatterns;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.helpers.AnimationsHelper;
import com.beemdevelopment.aegis.helpers.MetricsHelper;
import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback;
import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.helpers.VibrationHelper;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.ui.glide.GlideHelper;
import com.beemdevelopment.aegis.ui.models.ErrorCardInfo;
Expand Down Expand Up @@ -66,13 +68,13 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private Listener _listener;
private SimpleItemTouchHelperCallback _touchCallback;
private ItemTouchHelper _touchHelper;
private VibrationHelper _vibrationHelper;

private RecyclerView _recyclerView;
private RecyclerView.ItemDecoration _itemDecoration;
private ViewPreloadSizeProvider<VaultEntry> _preloadSizeProvider;
private TotpProgressBar _progressBar;
private boolean _showProgress;
private boolean _showExpirationState;
private ViewMode _viewMode;
private LinearLayout _emptyStateView;

Expand All @@ -95,6 +97,7 @@ public void onDestroy() {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_entry_list_view, container, false);
_progressBar = view.findViewById(R.id.progressBar);
_vibrationHelper = new VibrationHelper(getContext());

// set up the recycler view
_recyclerView = view.findViewById(R.id.rvKeyProfiles);
Expand Down Expand Up @@ -144,12 +147,26 @@ public int getSpanSize(int position) {
@Override
public void onRefresh() {
refresh(false);

if (_recyclerView.isShown()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this check now that we stop the refresher when the activity is not visible?

_vibrationHelper.vibratePattern(getContext(), VibrationPatterns.REFRESH_CODE);
}
}

@Override
public void onExpiring() {
_vibrationHelper.vibratePattern(getContext(), VibrationPatterns.EXPIRING);
}

@Override
public long getMillisTillNextRefresh() {
return TotpInfo.getMillisTillNextRotation(_adapter.getMostFrequentPeriod());
}

@Override
public long getPeriodMillis() {
return _adapter.getMostFrequentPeriod() * 1000L;
}
});

final int rvInitialPaddingLeft = _recyclerView.getPaddingLeft();
Expand Down Expand Up @@ -191,6 +208,14 @@ public void onDestroyView() {
super.onDestroyView();
}

public void onRefreshStop() {
_refresher.stop();
}

public void onRefreshStart() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're now unconditionally starting the refresher from MainActivity, I think we need to check whether _adapter.getMostFrequentPeriod() returns -1 here and if so not start the refresher. Otherwise, this causes a tight loop in the refresher in case there are no entries in the vault yet. This is also the reason why the instrumented tests are failing.

_refresher.start();
}

public void setGroups(Collection<VaultGroup> groups) {
_adapter.setGroups(groups);
updateDividerDecoration();
Expand Down Expand Up @@ -353,11 +378,11 @@ public void onPeriodUniformityChanged(boolean isUniform, int period) {
_progressBar.setVisibility(View.VISIBLE);
_progressBar.setPeriod(period);
_progressBar.start();
_refresher.start();
onRefreshStart();
} else {
_progressBar.setVisibility(View.GONE);
_progressBar.stop();
_refresher.stop();
onRefreshStop();
}
}

Expand Down Expand Up @@ -389,7 +414,6 @@ public void setShowNextCode(boolean showNextCode) {
}

public void setShowExpirationState(boolean showExpirationState) {
_showExpirationState = showExpirationState;
_adapter.setShowExpirationState(showExpirationState);
}

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@
<string name="note" comment="Users can add a note to an entry">Note</string>
<string name="clear">Clear</string>

<string name="pref_haptic_feedback_summary">Make your device vibrate when codes are refreshing</string>
<string name="pref_haptic_feedback_title">Haptic feedback</string>
<string name="pref_highlight_entry_title">Highlight tokens when tapped</string>
<string name="pref_highlight_entry_summary">Make tokens easier to distinguish from each other by temporarily highlighting them when tapped</string>
<string name="pref_minimize_on_copy_title">Minimize on copy</string>
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/xml/preferences_behavior.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
android:title="@string/pref_copy_behavior_title"
app:iconSpaceReserved="false"/>

<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_haptic_feedback"
android:title="@string/pref_haptic_feedback_title"
android:summary="@string/pref_haptic_feedback_summary"
app:iconSpaceReserved="false"/>

<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_highlight_entry"
Expand Down
Loading