Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eb07f6eb authored by Wilson Wu's avatar Wilson Wu Committed by Android (Google) Code Review
Browse files

Merge "Support custom vibration in haptic-only mode" into main

parents d5c78beb 21de19dc
Loading
Loading
Loading
Loading
+18 −2
Original line number Diff line number Diff line
@@ -31,7 +31,9 @@ import android.content.res.Resources;
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 @@ public class Ringer {
    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 @@ public class Ringer {

        mAudioManager = mContext.getSystemService(AudioManager.class);
        mFlags = featureFlags;
        mRingtoneVibrationSupported = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported);
    }

    @VisibleForTesting
@@ -420,6 +425,9 @@ public class Ringer {
            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 @@ public class Ringer {
                    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 @@ public class Ringer {
   }

    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) {
+41 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.media.AudioAttributes;
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.VibrationAttributes;
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 @@ public class RingerTest extends TelecomTestCase {
    @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 @@ public class RingerTest extends TelecomTestCase {
                .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