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

Commit a2e55eef authored by Edgar Arriaga's avatar Edgar Arriaga
Browse files

Reuse ringtone instead of creating multiple when playing

Current patch passes a ringer instead of a factory through
the play related operations to avoid unadvertedly creating
multiple ringers during the flow which harms performance.

Bug: 240621827
Test: Manual hear ringtone when receiving call on multiple options
Test: atest RingerTest
Test: atest RingtoneTest

Change-Id: I669003d3bab440621985e725405b54a32716a7e3
parent 642e11a3
Loading
Loading
Loading
Loading
+11 −100
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.media.VolumeShaper;
import android.net.Uri;
import android.os.Handler;
@@ -27,13 +28,10 @@ import android.os.HandlerThread;
import android.os.Message;
import android.telecom.Log;
import android.telecom.Logging.Session;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;

import java.util.concurrent.CompletableFuture;

/**
 * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
 * used from the main thread.
@@ -50,12 +48,6 @@ public class AsyncRingtonePlayer {
    /** The current ringtone. Only used by the ringtone thread. */
    private Ringtone mRingtone;

    /**
     * CompletableFuture which signals a caller when we know whether a ringtone will play haptics
     * or not.
     */
    private CompletableFuture<Boolean> mHapticsFuture = null;

    public AsyncRingtonePlayer() {
        // Empty
    }
@@ -65,35 +57,14 @@ public class AsyncRingtonePlayer {
     * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change
     * the volume of the ringtone as it plays.
     *
     * @param factory The {@link RingtoneFactory}.
     * @param incomingCall The ringing {@link Call}.
     * @param volumeShaperConfig An optional {@link VolumeShaper.Configuration} which is applied to
     *                           the ringtone to change its volume while it rings.
     * @param isVibrationEnabled {@code true} if the settings and DND configuration of the device
     *                           is such that the vibrator should be used, {@code false} otherwise.
     * @return A {@link CompletableFuture} which on completion indicates whether or not the ringtone
     *         has a haptic track.  {@code True} indicates that a haptic track is present on the
     *         ringtone; in this case the default vibration in {@link Ringer} should not be played.
     *         {@code False} indicates that a haptic track is NOT present on the ringtone;
     *         in this case the default vibration in {@link Ringer} should be trigger if needed.
     * @param ringtone The {@link Ringtone}.
     */
    public @NonNull
    CompletableFuture<Boolean> play(RingtoneFactory factory, Call incomingCall,
            @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean isRingerAudible,
            boolean isVibrationEnabled) {
    public void play(@NonNull Ringtone ringtone) {
        Log.d(this, "Posting play.");
        if (mHapticsFuture == null) {
            mHapticsFuture = new CompletableFuture<>();
        }
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = factory;
        args.arg2 = incomingCall;
        args.arg3 = volumeShaperConfig;
        args.arg4 = isVibrationEnabled;
        args.arg5 = isRingerAudible;
        args.arg6 = Log.createSubsession();
        args.arg1 = ringtone;
        args.arg2 = Log.createSubsession();
        postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
        return mHapticsFuture;
    }

    /** Stops playing the ringtone. */
@@ -151,76 +122,23 @@ public class AsyncRingtonePlayer {
     * Starts the actual playback of the ringtone. Executes on ringtone-thread.
     */
    private void handlePlay(SomeArgs args) {
        RingtoneFactory factory = (RingtoneFactory) args.arg1;
        Call incomingCall = (Call) args.arg2;
        VolumeShaper.Configuration volumeShaperConfig = (VolumeShaper.Configuration) args.arg3;
        boolean isVibrationEnabled = (boolean) args.arg4;
        boolean isRingerAudible = (boolean) args.arg5;
        Session session = (Session) args.arg6;
        Ringtone ringtone = (Ringtone) args.arg1;
        Session session = (Session) args.arg2;
        args.recycle();

        Log.continueSession(session, "ARP.hP");
        try {
            // don't bother with any of this if there is an EVENT_STOP waiting.
            if (mHandler.hasMessages(EVENT_STOP)) {
                completeHapticFuture(false /* ringtoneHasHaptics */);
                return;
            }

            // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected.
            // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
            // Use haptic-only ringtone or do not play anything.
            if (!isRingerAudible || Uri.EMPTY.equals(incomingCall.getRingtone())) {
                if (isVibrationEnabled) {
                    setRingtone(factory.getHapticOnlyRingtone());
                    if (mRingtone == null) {
                        completeHapticFuture(false /* ringtoneHasHaptics */);
                        return;
                    }
                } else {
                    setRingtone(null);
                    completeHapticFuture(false /* ringtoneHasHaptics */);
                return;
            }
            }

            ThreadUtil.checkNotOnMainThread();
            Log.i(this, "handlePlay: Play ringtone.");

            if (mRingtone == null) {
                setRingtone(factory.getRingtone(incomingCall, volumeShaperConfig));
                if (mRingtone == null) {
                    Uri ringtoneUri = incomingCall.getRingtone();
                    String ringtoneUriString = (ringtoneUri == null) ? "null" :
                            ringtoneUri.toSafeString();
                    Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
                            "factory. Skipping ringing. Uri was: " + ringtoneUriString);
                    completeHapticFuture(false /* ringtoneHasHaptics */);
                    return;
                }
            }

            // With the ringtone to play now known, we can determine if it has haptic channels or
            // not; we will complete the haptics future so the default vibration code in Ringer can
            // know whether to trigger the vibrator.
            if (mHapticsFuture != null && !mHapticsFuture.isDone()) {
                boolean hasHaptics = factory.hasHapticChannels(mRingtone);
                Log.i(this, "handlePlay: hasHaptics=%b, isVibrationEnabled=%b", hasHaptics,
                        isVibrationEnabled);
                SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
                if (hasHaptics && (volumeShaperConfig == null
                        || systemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())) {
                    AudioAttributes attributes = mRingtone.getAudioAttributes();
                    Log.d(this, "handlePlay: %s haptic channel",
                            (isVibrationEnabled ? "unmuting" : "muting"));
                    mRingtone.setAudioAttributes(
                            new AudioAttributes.Builder(attributes)
                                    .setHapticChannelsMuted(!isVibrationEnabled)
                                    .build());
                }
                completeHapticFuture(hasHaptics);
            }

            setRingtone(ringtone);
            Uri uri = mRingtone.getUri();
            String uriString = (uri != null ? uri.toSafeString() : "");
            Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString);
            mRingtone.setLooping(true);
            if (mRingtone.isPlaying()) {
                Log.d(this, "Ringtone already playing.");
@@ -268,11 +186,4 @@ public class AsyncRingtonePlayer {
        }
        mRingtone = ringtone;
    }

    private void completeHapticFuture(boolean ringtoneHasHaptics) {
        if (mHapticsFuture != null) {
            mHapticsFuture.complete(ringtoneHasHaptics);
            mHapticsFuture = null;
        }
    }
}
+26 −19
Original line number Diff line number Diff line
@@ -19,12 +19,12 @@ package com.android.server.telecom;
import android.media.AudioManager;
import android.os.Looper;
import android.os.Message;
import android.os.Trace;
import android.telecom.Log;
import android.telecom.Logging.Runnable;
import android.telecom.Logging.Session;
import android.util.LocalLog;
import android.util.SparseArray;

import com.android.internal.util.IState;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.State;
@@ -370,17 +370,20 @@ public class CallAudioModeStateMachine extends StateMachine {
        private boolean mHasFocus = false;

        private void tryStartRinging() {
            Trace.traceBegin(Trace.TRACE_TAG_AUDIO, "CallAudioMode.tryStartRinging");
            try {
                if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
                Log.i(LOG_TAG, "RingingFocusState#tryStartRinging -- audio focus previously"
                    Log.i(LOG_TAG,
                        "RingingFocusState#tryStartRinging -- audio focus previously"
                            + " acquired and ringtone already playing -- skipping.");
                    return;
                }

                if (mCallAudioManager.startRinging()) {
                mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode -- this
                // trips up the audio system.
                    mAudioManager.requestAudioFocusForCall(
                        AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                    // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
                    // this trips up the audio system.
                    if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
                        mAudioManager.setMode(AudioManager.MODE_RINGTONE);
                        mLocalLog.log("Mode MODE_RINGTONE");
@@ -389,7 +392,11 @@ public class CallAudioModeStateMachine extends StateMachine {
                        CallAudioRouteStateMachine.RINGING_FOCUS);
                    mHasFocus = true;
                } else {
                Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
                    Log.i(
                        LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_AUDIO);
            }
        }

+139 −101

File changed.

Preview size limit exceeded, changes collapsed.

+11 −10
Original line number Diff line number Diff line
@@ -65,7 +65,9 @@ public class RingtoneFactory {
    }

    public Ringtone getRingtone(Call incomingCall,
            @Nullable VolumeShaper.Configuration volumeShaperConfig) {
        @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) {
        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted);

        // Use the default ringtone of the work profile if the contact is a work profile contact.
        Context userContext = isWorkContact(incomingCall) ?
                getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
@@ -73,8 +75,6 @@ public class RingtoneFactory {
        Uri ringtoneUri = incomingCall.getRingtone();
        Ringtone ringtone = null;

        AudioAttributes audioAttrs = getRingtoneAudioAttributes();

        if(ringtoneUri != null && userContext != null) {
            // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
            try {
@@ -101,9 +101,11 @@ public class RingtoneFactory {
                    Log.i(this, "getRingtone: Settings.System.DEFAULT_RINGTONE_URI is null.");
                }
            }

            if (defaultRingtoneUri == null) {
                return null;
            }

            try {
                ringtone = RingtoneManager.getRingtone(
                    contextToUse, defaultRingtoneUri, volumeShaperConfig, audioAttrs);
@@ -114,24 +116,23 @@ public class RingtoneFactory {
        return ringtone;
    }

    public AudioAttributes getRingtoneAudioAttributes() {
    public AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
        return new AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setHapticChannelsMuted(hapticChannelsMuted)
            .build();
    }

    public Ringtone getRingtone(Call incomingCall) {
        return getRingtone(incomingCall, null);
    }

    /** Returns a ringtone to be used when ringer is not audible for the incoming call. */
    @Nullable
    public Ringtone getHapticOnlyRingtone() {
        Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
                com.android.internal.R.string.config_defaultRingtoneVibrationSound));
        AudioAttributes audioAttrs = getRingtoneAudioAttributes();
        Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri, null, audioAttrs);
        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
            /* hapticChannelsMuted */ false);
        Ringtone ringtone = RingtoneManager.getRingtone(
            mContext, ringtoneUri, /* volumeShaperConfig */ null, audioAttrs);
        if (ringtone != null) {
            // Make sure the sound is muted.
            ringtone.setVolume(0);
+112 −92

File changed.

Preview size limit exceeded, changes collapsed.