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
This commit is contained in:
John Li
2020-11-10 20:02:51 +08:00
parent f24dc017ef
commit 8622d91973
2 changed files with 88 additions and 0 deletions

View File

@@ -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);
}

View File

@@ -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) {