Refine default vibration behavior for vibrate-only
Keep the classic vibration effect as default for
haptic-only mode. Support the custom vibration playback
only if the user choose a new vibration via settings.
Also if the user choose a new vibration, avoid haptic channel
mute when ramping ringer enabled.
Flag: android.media.audio.enable_ringtone_haptics_customization
Bug: 394231980
Test: atest RingerTest
Change-Id: I3dab1edc3a5d7a64a65d6350a99ad9517d999f2b
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index bfaadf0..d0fd201 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -31,6 +31,7 @@
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
+import android.media.RingtoneManager;
import android.media.Utils;
import android.media.VolumeShaper;
import android.media.audio.Flags;
@@ -431,6 +432,11 @@
&& isVibratorEnabled) {
Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled");
hapticChannelsMuted = true;
+ if (useCustomVibration(foregroundCall)) {
+ Log.i(this,
+ "Not muted haptic channel for customization when apply ramping ringer");
+ hapticChannelsMuted = false;
+ }
} else if (hapticChannelsMuted) {
Log.i(this,
"Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s",
@@ -442,7 +448,7 @@
if (!isHapticOnly) {
ringtoneInfoSupplier = () -> mRingtoneFactory.getRingtone(
foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);
- } else if (Flags.enableRingtoneHapticsCustomization() && mRingtoneVibrationSupported) {
+ } else if (useCustomVibration(foregroundCall)) {
ringtoneInfoSupplier = () -> mRingtoneFactory.getRingtone(
foregroundCall, null, false);
}
@@ -521,6 +527,21 @@
}
}
+ private boolean useCustomVibration(@NonNull Call foregroundCall) {
+ return Flags.enableRingtoneHapticsCustomization() && mRingtoneVibrationSupported
+ && hasExplicitVibration(foregroundCall);
+ }
+
+ private boolean hasExplicitVibration(@NonNull Call foregroundCall) {
+ final Uri ringtoneUri = foregroundCall.getRingtone();
+ if (ringtoneUri != null) {
+ // TODO(b/399265235) : Avoid this hidden API access for mainline
+ return Utils.hasVibration(ringtoneUri);
+ }
+ return Utils.hasVibration(RingtoneManager.getActualDefaultRingtoneUri(
+ mContext, RingtoneManager.TYPE_RINGTONE));
+ }
+
/**
* Try to reserve the vibrator for this call, returning false if it's already committed.
* The vibration will be started by AsyncRingtonePlayer to ensure timing is aligned with the
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index ad62643..d9bf6e1 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,6 +47,7 @@
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
+import android.media.RingtoneManager;
import android.media.VolumeShaper;
import android.media.audio.Flags;
import android.net.Uri;
@@ -65,6 +67,7 @@
import android.telecom.TelecomManager;
import android.util.Pair;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -85,6 +88,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;
@@ -846,8 +850,12 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION)
public void testNoVibrateForSilentRingtoneIfRingtoneHasVibration() throws Exception {
+ final Context context = ApplicationProvider.getApplicationContext();
+ Uri defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context,
+ RingtoneManager.TYPE_RINGTONE);
+ assumeNotNull(defaultRingtoneUri);
Uri FAKE_RINGTONE_VIBRATION_URI =
- FAKE_RINGTONE_URI.buildUpon().appendQueryParameter(
+ defaultRingtoneUri.buildUpon().appendQueryParameter(
VIBRATION_PARAM, FAKE_VIBRATION_URI.toString()).build();
Ringtone mockRingtone = mock(Ringtone.class);
Pair<Uri, Ringtone> ringtoneInfo = new Pair(FAKE_RINGTONE_VIBRATION_URI, mockRingtone);
@@ -856,21 +864,61 @@
.thenReturn(ringtoneInfo);
mComponentContextFixture.putBooleanResource(
com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported, true);
- createRingerUnderTest(); // Needed after mock the config.
+ try {
+ RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE,
+ FAKE_RINGTONE_VIBRATION_URI);
+ createRingerUnderTest(); // Needed after mock the config.
- mRingerUnderTest.startCallWaiting(mockCall1);
- when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
- enableVibrationWhenRinging();
- assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+ when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+ enableVibrationWhenRinging();
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
- verify(mockRingtoneFactory, atLeastOnce())
- .getRingtone(any(Call.class), eq(null), eq(false));
- verifyNoMoreInteractions(mockRingtoneFactory);
- verify(mockTonePlayer).stopTone();
- // Skip vibration play in Ringer if a vibration was specified to the ringtone
- verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
- any(VibrationAttributes.class));
+ verify(mockRingtoneFactory, atLeastOnce())
+ .getRingtone(any(Call.class), eq(null), eq(false));
+ verifyNoMoreInteractions(mockRingtoneFactory);
+ verify(mockTonePlayer).stopTone();
+ // Skip vibration play in Ringer if a vibration was specified to the ringtone
+ verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
+ any(VibrationAttributes.class));
+ } finally {
+ // Restore the default ringtone Uri
+ RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE,
+ defaultRingtoneUri);
+ }
+ }
+
+ @SmallTest
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION)
+ public void testNotMuteHapticChannelWithRampingRinger() throws Exception {
+ final Context context = ApplicationProvider.getApplicationContext();
+ Uri defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context,
+ RingtoneManager.TYPE_RINGTONE);
+ assumeNotNull(defaultRingtoneUri);
+ Uri FAKE_RINGTONE_VIBRATION_URI = defaultRingtoneUri.buildUpon().appendQueryParameter(
+ VIBRATION_PARAM, FAKE_VIBRATION_URI.toString()).build();
+ mComponentContextFixture.putBooleanResource(
+ com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported, true);
+ ArgumentCaptor<Boolean> muteHapticChannelCaptor = ArgumentCaptor.forClass(Boolean.class);
+ try {
+ RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE,
+ FAKE_RINGTONE_VIBRATION_URI);
+ createRingerUnderTest(); // Needed after mock the config.
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ ensureRingerIsAudible();
+ enableRampingRinger();
+ enableVibrationWhenRinging();
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+ verify(mockRingtoneFactory, atLeastOnce()).getRingtone(any(Call.class),
+ nullable(VolumeShaper.Configuration.class), muteHapticChannelCaptor.capture());
+ assertFalse(muteHapticChannelCaptor.getValue());
+ } finally {
+ // Restore the default ringtone Uri
+ RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE,
+ defaultRingtoneUri);
+ }
}
/**