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);
+        }
     }
 
     /**