Using View.performHapticFeedback on Volume Dialog Impl when the silent and vibrate-only icons are pressed
Moving towards the one-way API that can trigger haptic feedback from the UI thread. The feedback on touch events that activate silent mode and vibrate-only mode now trigger TOGGLE_OFF and TOGGLE_ON haptics, respectively. This is per the recommended constants of the new API, which are more suitable for the interactions they represent. Caveat: The case when the onStateChangedH callback handles dragging the ringer volume slider to 0 in the settings activity does not trigger haptics. This will be fixed in the future (b/290642122). To avoid inconsistent haptics, a flag was introduced to activate the new API. Test: Manual. To test, enable the oneway_haptics_api_migration flag and press the volume keys on the device. When the volume dialog appears, click the top icons for silent and vibrate-only mode. The vibrate mode triggers the vibration of the TOGGLE_ON haptic signal and silent mode triggers the TOGGLE_OFF signal. Sliding the ringer volume to 0 in settings will not produce haptic feedback (this is expected). Bug: 245528624 Change-Id: I427faf4cc415dfbaaf1a0a1f5d94e57ac734b7f5
This commit is contained in:
@@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
|
||||
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
|
||||
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
|
||||
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
|
||||
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
|
||||
|
||||
@@ -82,6 +83,7 @@ import android.util.Slog;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.AccessibilityDelegate;
|
||||
@@ -120,6 +122,7 @@ import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.Prefs;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.VolumeDialog;
|
||||
@@ -300,6 +303,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
private final DevicePostureController mDevicePostureController;
|
||||
private @DevicePostureController.DevicePostureInt int mDevicePosture;
|
||||
private int mOrientation;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
|
||||
public VolumeDialogImpl(
|
||||
Context context,
|
||||
@@ -315,7 +319,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
CsdWarningDialog.Factory csdWarningDialogFactory,
|
||||
DevicePostureController devicePostureController,
|
||||
Looper looper,
|
||||
DumpManager dumpManager) {
|
||||
DumpManager dumpManager,
|
||||
FeatureFlags featureFlags) {
|
||||
mFeatureFlags = featureFlags;
|
||||
mContext =
|
||||
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
|
||||
mHandler = new H(looper);
|
||||
@@ -1319,12 +1325,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
|
||||
private void provideTouchFeedbackH(int newRingerMode) {
|
||||
VibrationEffect effect = null;
|
||||
int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
|
||||
switch (newRingerMode) {
|
||||
case RINGER_MODE_NORMAL:
|
||||
mController.scheduleTouchFeedback();
|
||||
break;
|
||||
case RINGER_MODE_SILENT:
|
||||
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||
hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
|
||||
break;
|
||||
case RINGER_MODE_VIBRATE:
|
||||
// Feedback handled by onStateChange, for feedback both when user toggles
|
||||
@@ -1332,8 +1340,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
break;
|
||||
default:
|
||||
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
|
||||
hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
|
||||
}
|
||||
if (effect != null) {
|
||||
if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
|
||||
mDialogView.performHapticFeedback(hapticConstant);
|
||||
} else if (effect != null) {
|
||||
mController.vibrate(effect);
|
||||
}
|
||||
}
|
||||
@@ -1770,7 +1781,22 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
|
||||
&& mState.ringerModeInternal != -1
|
||||
&& mState.ringerModeInternal != state.ringerModeInternal
|
||||
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
|
||||
mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
|
||||
|
||||
if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
|
||||
if (mShowing) {
|
||||
// The dialog view is responsible for triggering haptics in the oneway API
|
||||
mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
|
||||
}
|
||||
/*
|
||||
TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
|
||||
enabled by dragging the volume slider of Settings to a value of 0. This must be
|
||||
handled by view Slices in Settings whilst using the performHapticFeedback API.
|
||||
*/
|
||||
|
||||
} else {
|
||||
// Old behavior only active if the oneway API is not used.
|
||||
mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
|
||||
}
|
||||
}
|
||||
mState = state;
|
||||
mDynamic.clear();
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.os.Looper;
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.VolumeDialog;
|
||||
@@ -61,7 +62,8 @@ public interface VolumeModule {
|
||||
InteractionJankMonitor interactionJankMonitor,
|
||||
CsdWarningDialog.Factory csdFactory,
|
||||
DevicePostureController devicePostureController,
|
||||
DumpManager dumpManager) {
|
||||
DumpManager dumpManager,
|
||||
FeatureFlags featureFlags) {
|
||||
VolumeDialogImpl impl = new VolumeDialogImpl(
|
||||
context,
|
||||
volumeDialogController,
|
||||
@@ -76,7 +78,8 @@ public interface VolumeModule {
|
||||
csdFactory,
|
||||
devicePostureController,
|
||||
Looper.getMainLooper(),
|
||||
dumpManager);
|
||||
dumpManager,
|
||||
featureFlags);
|
||||
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
|
||||
impl.setAutomute(true);
|
||||
impl.setSilentMode(false);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.volume;
|
||||
|
||||
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
|
||||
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
|
||||
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
|
||||
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
|
||||
@@ -51,6 +52,7 @@ import com.android.systemui.Prefs;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FakeFeatureFlags;
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.VolumeDialogController;
|
||||
@@ -117,6 +119,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
}
|
||||
};
|
||||
|
||||
private FakeFeatureFlags mFeatureFlags;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
@@ -132,6 +136,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
|
||||
mConfigurationController = new FakeConfigurationController();
|
||||
|
||||
mFeatureFlags = new FakeFeatureFlags();
|
||||
|
||||
mDialog = new VolumeDialogImpl(
|
||||
getContext(),
|
||||
mVolumeDialogController,
|
||||
@@ -146,7 +152,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
mCsdWarningDialogFactory,
|
||||
mPostureController,
|
||||
mTestableLooper.getLooper(),
|
||||
mDumpManager);
|
||||
mDumpManager,
|
||||
mFeatureFlags);
|
||||
mDialog.init(0, null);
|
||||
State state = createShellState();
|
||||
mDialog.onStateChangedH(state);
|
||||
@@ -254,6 +261,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testVibrateOnRingerChangedToVibrate() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
|
||||
final State initialSilentState = new State();
|
||||
initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
|
||||
|
||||
@@ -273,8 +281,31 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
verify(mVolumeDialogController).vibrate(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
|
||||
final State initialSilentState = new State();
|
||||
initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
|
||||
|
||||
final State vibrateState = new State();
|
||||
vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
|
||||
|
||||
// change ringer to silent
|
||||
mDialog.onStateChangedH(initialSilentState);
|
||||
|
||||
// expected: shouldn't call vibrate yet
|
||||
verify(mVolumeDialogController, never()).vibrate(any());
|
||||
|
||||
// changed ringer to vibrate
|
||||
mDialog.onStateChangedH(vibrateState);
|
||||
|
||||
// expected: vibrate method of controller is not used
|
||||
verify(mVolumeDialogController, never()).vibrate(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoVibrateOnRingerInitialization() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = -1;
|
||||
|
||||
@@ -291,8 +322,43 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
verify(mVolumeDialogController, never()).vibrate(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = -1;
|
||||
|
||||
// ringer not initialized yet:
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
|
||||
final State vibrateState = new State();
|
||||
vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
|
||||
|
||||
// changed ringer to vibrate
|
||||
mDialog.onStateChangedH(vibrateState);
|
||||
|
||||
// shouldn't call vibrate on the controller either
|
||||
verify(mVolumeDialogController, never()).vibrate(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectVibrateFromDrawer() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
|
||||
mActiveRinger.performClick();
|
||||
mDrawerVibrate.performClick();
|
||||
|
||||
// Make sure we've actually changed the ringer mode.
|
||||
verify(mVolumeDialogController, times(1)).setRingerMode(
|
||||
AudioManager.RINGER_MODE_VIBRATE, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectVibrateFromDrawer_OnewayAPI_On() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
@@ -307,6 +373,22 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testSelectMuteFromDrawer() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
|
||||
mActiveRinger.performClick();
|
||||
mDrawerMute.performClick();
|
||||
|
||||
// Make sure we've actually changed the ringer mode.
|
||||
verify(mVolumeDialogController, times(1)).setRingerMode(
|
||||
AudioManager.RINGER_MODE_SILENT, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectMuteFromDrawer_OnewayAPI_On() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
@@ -321,6 +403,22 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testSelectNormalFromDrawer() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
|
||||
mActiveRinger.performClick();
|
||||
mDrawerNormal.performClick();
|
||||
|
||||
// Make sure we've actually changed the ringer mode.
|
||||
verify(mVolumeDialogController, times(1)).setRingerMode(
|
||||
AudioManager.RINGER_MODE_NORMAL, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectNormalFromDrawer_OnewayAPI_On() {
|
||||
mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
|
||||
final State initialUnsetState = new State();
|
||||
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
|
||||
mDialog.onStateChangedH(initialUnsetState);
|
||||
@@ -383,7 +481,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
mCsdWarningDialogFactory,
|
||||
devicePostureController,
|
||||
mTestableLooper.getLooper(),
|
||||
mDumpManager
|
||||
mDumpManager,
|
||||
mFeatureFlags
|
||||
);
|
||||
dialog.init(0 , null);
|
||||
|
||||
@@ -423,7 +522,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
mCsdWarningDialogFactory,
|
||||
devicePostureController,
|
||||
mTestableLooper.getLooper(),
|
||||
mDumpManager
|
||||
mDumpManager,
|
||||
mFeatureFlags
|
||||
);
|
||||
dialog.init(0, null);
|
||||
|
||||
@@ -462,7 +562,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
mCsdWarningDialogFactory,
|
||||
devicePostureController,
|
||||
mTestableLooper.getLooper(),
|
||||
mDumpManager
|
||||
mDumpManager,
|
||||
mFeatureFlags
|
||||
);
|
||||
dialog.init(0, null);
|
||||
|
||||
@@ -503,7 +604,9 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
||||
mCsdWarningDialogFactory,
|
||||
mPostureController,
|
||||
mTestableLooper.getLooper(),
|
||||
mDumpManager);
|
||||
mDumpManager,
|
||||
mFeatureFlags
|
||||
);
|
||||
dialog.init(0, null);
|
||||
|
||||
verify(mPostureController, never()).removeCallback(any());
|
||||
|
||||
Reference in New Issue
Block a user