Support custom vibration in haptic-only mode

With CL[1], Ringtone start to support the custom
vibration play. If the device is in vibrate-only
mode, the Ringer returns the ringtone Uri as audible
flow so Ringtone can use it for Vibration support.

[1]: I8fc4f49e3801caec1dc2b0b611dd5b3854c6395f

Flag: android.media.audio.enable_ringtone_haptics_customization
Bug: 358523092
Test: atest RingerTest
Change-Id: Ia178df833dfafd8d4da49d0ed90c0d740e5d3108
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 1c26017..c309dd5 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -31,7 +31,9 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.Ringtone;
+import android.media.Utils;
 import android.media.VolumeShaper;
+import android.media.audio.Flags;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -188,6 +190,7 @@
     private final VibrationEffectProxy mVibrationEffectProxy;
     private final boolean mIsHapticPlaybackSupportedByDevice;
     private final FeatureFlags mFlags;
+    private final boolean mRingtoneVibrationSupported;
     /**
      * For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete
      * the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}.
@@ -259,6 +262,8 @@
 
         mAudioManager = mContext.getSystemService(AudioManager.class);
         mFlags = featureFlags;
+        mRingtoneVibrationSupported = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported);
     }
 
     @VisibleForTesting
@@ -420,6 +425,9 @@
             if (!isHapticOnly) {
                 ringtoneInfoSupplier = () -> mRingtoneFactory.getRingtone(
                         foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);
+            } else if (Flags.enableRingtoneHapticsCustomization() && mRingtoneVibrationSupported) {
+                ringtoneInfoSupplier = () -> mRingtoneFactory.getRingtone(
+                        foregroundCall, null, false);
             }
 
             // If vibration will be done, reserve the vibrator.
@@ -471,7 +479,8 @@
                     boolean isUsingAudioCoupledHaptics =
                             !finalHapticChannelsMuted && ringtone != null
                                     && ringtone.hasHapticChannels();
-                    vibrateIfNeeded(isUsingAudioCoupledHaptics, foregroundCall, vibrationEffect);
+                    vibrateIfNeeded(isUsingAudioCoupledHaptics, foregroundCall, vibrationEffect,
+                            ringtoneUri);
                 } finally {
                     // This is used to signal to tests that the async play() call has completed.
                     if (mBlockOnRingingFuture != null) {
@@ -523,13 +532,20 @@
    }
 
     private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, Call foregroundCall,
-            VibrationEffect effect) {
+            VibrationEffect effect, Uri ringtoneUri) {
         if (isUsingAudioCoupledHaptics) {
             Log.addEvent(
                 foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics");
             return;
         }
 
+        if (Flags.enableRingtoneHapticsCustomization() && mRingtoneVibrationSupported
+                && Utils.hasVibration(ringtoneUri)) {
+            Log.addEvent(
+                    foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using custom haptics");
+            return;
+        }
+
         synchronized (mLock) {
             // Ensure the reservation is live. The mIsVibrating check should be redundant.
             if (foregroundCall == mVibratingCall && !mIsVibrating) {
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 1510e0c..c4d9678 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -47,6 +47,7 @@
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.VolumeShaper;
+import android.media.audio.Flags;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -55,8 +56,10 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.util.Pair;
@@ -91,7 +94,14 @@
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
+
+    private static final Uri FAKE_VIBRATION_URI = Uri.parse("file://media/fake/vibration/1729");
+
+    private static final String VIBRATION_PARAM = "vibration_uri";
     // Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
     // device configuration for ringtone URIs. The actual Uri can be verified via the
     // VibrationEffectProxy mock invocation.
@@ -805,6 +815,37 @@
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
+    @SmallTest
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION)
+    public void testNoVibrateForSilentRingtoneIfRingtoneHasVibration() throws Exception {
+        Uri FAKE_RINGTONE_VIBRATION_URI =
+                FAKE_RINGTONE_URI.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);
+        when(mockRingtoneFactory.getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+                .thenReturn(ringtoneInfo);
+        mComponentContextFixture.putBooleanResource(
+                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported, true);
+        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));
+
+        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));
+    }
+
     /**
      * Call startRinging and wait for its effects to have played out, to allow reliable assertions
      * after it. The effects are generally "start playing ringtone" and "start vibration" - not