From 8622d91973041c27e96e7fb7e573dfa3abceb782 Mon Sep 17 00:00:00 2001 From: John Li Date: Tue, 10 Nov 2020 20:02:51 +0800 Subject: [PATCH 1/2] Perform haptic feedback when the seekbar snaps. - add haptic feedback mode to perform haptic feedback as the seekbar's progress value is updated. - add haptic feedback mode to perform haptic feedback as the seekbar's progress value is equal to the min/max value Bug: 172900709 Test: make -j42 RunSettingsRoboTests ROBOTEST_FILTER=SeekBarPreferenceTest Change-Id: I9378313d366f09bb8e7069d9240fde24af318c53 --- .../settings/widget/SeekBarPreference.java | 28 +++++++++ .../widget/SeekBarPreferenceTest.java | 60 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/src/com/android/settings/widget/SeekBarPreference.java b/src/com/android/settings/widget/SeekBarPreference.java index ac2838271c4..47bb28608d5 100644 --- a/src/com/android/settings/widget/SeekBarPreference.java +++ b/src/com/android/settings/widget/SeekBarPreference.java @@ -16,6 +16,8 @@ package com.android.settings.widget; +import static android.view.HapticFeedbackConstants.CLOCK_TICK; + import android.content.Context; import android.content.res.TypedArray; import android.os.Parcel; @@ -39,12 +41,17 @@ import com.android.settingslib.RestrictedPreference; public class SeekBarPreference extends RestrictedPreference implements OnSeekBarChangeListener, View.OnKeyListener { + public static final int HAPTIC_FEEDBACK_MODE_NONE = 0; + public static final int HAPTIC_FEEDBACK_MODE_ON_TICKS = 1; + public static final int HAPTIC_FEEDBACK_MODE_ON_ENDS = 2; + private int mProgress; private int mMax; private int mMin; private boolean mTrackingTouch; private boolean mContinuousUpdates; + private int mHapticFeedbackMode = HAPTIC_FEEDBACK_MODE_NONE; private int mDefaultProgress = -1; private SeekBar mSeekBar; @@ -235,6 +242,17 @@ public class SeekBarPreference extends RestrictedPreference mContinuousUpdates = continuousUpdates; } + /** + * Sets the haptic feedback mode. HAPTIC_FEEDBACK_MODE_ON_TICKS means to perform haptic feedback + * as the SeekBar's progress is updated; HAPTIC_FEEDBACK_MODE_ON_ENDS means to perform haptic + * feedback as the SeekBar's progress value is equal to the min/max value. + * + * @param hapticFeedbackMode the haptic feedback mode. + */ + public void setHapticFeedbackMode(int hapticFeedbackMode) { + mHapticFeedbackMode = hapticFeedbackMode; + } + private void setProgress(int progress, boolean notifyChanged) { if (progress > mMax) { progress = mMax; @@ -264,6 +282,16 @@ public class SeekBarPreference extends RestrictedPreference if (progress != mProgress) { if (callChangeListener(progress)) { setProgress(progress, false); + switch (mHapticFeedbackMode) { + case HAPTIC_FEEDBACK_MODE_ON_TICKS: + seekBar.performHapticFeedback(CLOCK_TICK); + break; + case HAPTIC_FEEDBACK_MODE_ON_ENDS: + if (progress == mMax || progress == mMin) { + seekBar.performHapticFeedback(CLOCK_TICK); + } + break; + } } else { seekBar.setProgress(mProgress); } diff --git a/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java index 42ed0746417..451b84bd882 100644 --- a/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java @@ -16,15 +16,21 @@ package com.android.settings.widget; +import static android.view.HapticFeedbackConstants.CLOCK_TICK; +import static android.view.HapticFeedbackConstants.CONTEXT_CLICK; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; import android.content.Context; import android.os.Bundle; import android.os.Parcelable; +import android.widget.SeekBar; import androidx.preference.PreferenceFragmentCompat; @@ -46,9 +52,11 @@ public class SeekBarPreferenceTest { private static final int MAX = 75; private static final int MIN = 5; private static final int PROGRESS = 16; + private static final int NEW_PROGRESS = 17; private Context mContext; private SeekBarPreference mSeekBarPreference; + private SeekBar mSeekBar; @Before public void setUp() { @@ -60,6 +68,11 @@ public class SeekBarPreferenceTest { mSeekBarPreference.setMin(MIN); mSeekBarPreference.setProgress(PROGRESS); mSeekBarPreference.setPersistent(false); + mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_NONE); + + mSeekBar = new SeekBar(mContext); + mSeekBar.setMax(MAX); + mSeekBar.setMin(MIN); } @Test @@ -118,6 +131,53 @@ public class SeekBarPreferenceTest { verify(mSeekBarPreference).setSeekBarStateDescription("test"); } + @Test + public void onProgressChanged_hapticFeedbackModeNone_clockTickFeedbackNotPerformed() { + mSeekBar.setProgress(NEW_PROGRESS); + when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true); + mSeekBar.performHapticFeedback(CONTEXT_CLICK); + + mSeekBarPreference.onProgressChanged(mSeekBar, NEW_PROGRESS, true); + + assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isNotEqualTo(CLOCK_TICK); + } + + @Test + public void onProgressChanged_hapticFeedbackModeOnTicks_clockTickFeedbackPerformed() { + mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS); + mSeekBar.setProgress(NEW_PROGRESS); + when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true); + mSeekBar.performHapticFeedback(CONTEXT_CLICK); + + mSeekBarPreference.onProgressChanged(mSeekBar, NEW_PROGRESS, true); + + assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK); + } + + @Test + public void onProgressChanged_hapticFeedbackModeOnEnds_clockTickFeedbackNotPerformed() { + mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS); + mSeekBar.setProgress(NEW_PROGRESS); + when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true); + mSeekBar.performHapticFeedback(CONTEXT_CLICK); + + mSeekBarPreference.onProgressChanged(mSeekBar, NEW_PROGRESS, true); + + assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isNotEqualTo(CLOCK_TICK); + } + + @Test + public void onProgressChanged_hapticFeedbackModeOnEndsAndMinValue_clockTickFeedbackPerformed() { + mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS); + mSeekBar.setProgress(MIN); + when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true); + mSeekBar.performHapticFeedback(CONTEXT_CLICK); + + mSeekBarPreference.onProgressChanged(mSeekBar, MIN, true); + + assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK); + } + public static class TestFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { From db2d3347d779ddb8563c26e2341244de200bd685 Mon Sep 17 00:00:00 2001 From: John Li Date: Fri, 13 Nov 2020 09:53:49 +0800 Subject: [PATCH 2/2] Add haptic feedback for the seekbar in Settings. - set HAPTIC_FEEDBACK_MODE_ON_TICKS for Gesture Navigation. - set HAPTIC_FEEDBACK_MODE_ON_TICKS for Battery Saver. - set HAPTIC_FEEDBACK_MODE_ON_ENDS for Reduce Bright Colors. - set HAPTIC_FEEDBACK_MODE_ON_ENDS for Night Light. Bug: 172900709 Test: manual Change-Id: I070a0f2e4cf690bf611f62c7a4a2bcef17e08571 --- .../ReduceBrightColorsIntensityPreferenceController.java | 1 + .../display/NightDisplayIntensityPreferenceController.java | 1 + .../batterysaver/BatterySaverScheduleSeekBarController.java | 1 + .../settings/gestures/GestureNavigationSettingsFragment.java | 2 ++ 4 files changed, 5 insertions(+) diff --git a/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java b/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java index b9a6d399529..09243e59bb0 100644 --- a/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java +++ b/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java @@ -45,6 +45,7 @@ public class ReduceBrightColorsIntensityPreferenceController extends SliderPrefe preference.setContinuousUpdates(true); preference.setMax(getMax()); preference.setMin(getMin()); + preference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS); updateState(preference); } diff --git a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java index 02dde406eec..0f8ecc515e1 100644 --- a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java +++ b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java @@ -62,6 +62,7 @@ public class NightDisplayIntensityPreferenceController extends SliderPreferenceC preference.setContinuousUpdates(true); preference.setMax(getMax()); preference.setMin(getMin()); + preference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS); } @Override diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java index 5442e7d02c8..fc9de07560f 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java @@ -60,6 +60,7 @@ public class BatterySaverScheduleSeekBarController implements mSeekBarPreference.setMax(MAX_SEEKBAR_VALUE); mSeekBarPreference.setMin(MIN_SEEKBAR_VALUE); mSeekBarPreference.setKey(KEY_BATTERY_SAVER_SEEK_BAR); + mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS); updateSeekBar(); } diff --git a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java index 95f4daa4c67..546581bd128 100644 --- a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java +++ b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java @@ -28,6 +28,7 @@ import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.LabeledSeekBarPreference; +import com.android.settings.widget.SeekBarPreference; import com.android.settingslib.search.SearchIndexable; /** @@ -115,6 +116,7 @@ public class GestureNavigationSettingsFragment extends DashboardFragment { private void initSeekBarPreference(final String key) { final LabeledSeekBarPreference pref = getPreferenceScreen().findPreference(key); pref.setContinuousUpdates(true); + pref.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS); final String settingsKey = key == LEFT_EDGE_SEEKBAR_KEY ? Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT