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

Commit a75567ae authored by Simon Bowden's avatar Simon Bowden
Browse files

Create a Builder for Ringtone, add unit tests.

Currently hidden until the full new API is ready, but convert private
factory methods from RingtoneManager to use it. This moves towards
making the Ringtone class less mutable, and particularly for all required
media player properties (esp AudioAttributes) being available for the
initial construction.

Adds an initial unit test covering basic properties and fallback, and
"documenting" what happens when the uri is null, which was unclear before.

Bug: Bug: 261571543
Test: manual, presubmit
Change-Id: I81d512e9b21b64a2cca0450cb85c52d8a2125865
parent abcf9857
Loading
Loading
Loading
Loading
+14 −8
Original line number Diff line number Diff line
@@ -42,18 +42,22 @@ public class LocalRingtonePlayer

    private final MediaPlayer mMediaPlayer;
    private final AudioAttributes mAudioAttributes;
    private final Ringtone.Injectables mInjectables;
    private final AudioManager mAudioManager;
    private final VolumeShaper mVolumeShaper;
    private HapticGenerator mHapticGenerator;

    private LocalRingtonePlayer(@NonNull MediaPlayer mediaPlayer,
            @NonNull AudioAttributes audioAttributes, @NonNull AudioManager audioManager,
            @Nullable HapticGenerator hapticGenerator, @Nullable VolumeShaper volumeShaper) {
            @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables,
            @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator,
            @Nullable VolumeShaper volumeShaper) {
        Objects.requireNonNull(mediaPlayer);
        Objects.requireNonNull(audioAttributes);
        Objects.requireNonNull(injectables);
        Objects.requireNonNull(audioManager);
        mMediaPlayer = mediaPlayer;
        mAudioAttributes = audioAttributes;
        mInjectables = injectables;
        mAudioManager = audioManager;
        mVolumeShaper = volumeShaper;
        mHapticGenerator = hapticGenerator;
@@ -67,6 +71,7 @@ public class LocalRingtonePlayer
    static LocalRingtonePlayer create(@NonNull Context context,
            @NonNull AudioManager audioManager, @NonNull Uri soundUri,
            @NonNull AudioAttributes audioAttributes,
            @NonNull Ringtone.Injectables injectables,
            @Nullable VolumeShaper.Configuration volumeShaperConfig,
            @Nullable AudioDeviceInfo preferredDevice, boolean initialHapticGeneratorEnabled,
            boolean initialLooping, float initialVolume) {
@@ -74,7 +79,7 @@ public class LocalRingtonePlayer
        Objects.requireNonNull(soundUri);
        Objects.requireNonNull(audioAttributes);
        Trace.beginSection("createLocalMediaPlayer");
        MediaPlayer mediaPlayer = new MediaPlayer();
        MediaPlayer mediaPlayer = injectables.newMediaPlayer();
        HapticGenerator hapticGenerator = null;
        try {
            mediaPlayer.setDataSource(context, soundUri);
@@ -83,7 +88,7 @@ public class LocalRingtonePlayer
            mediaPlayer.setLooping(initialLooping);
            mediaPlayer.setVolume(initialVolume);
            if (initialHapticGeneratorEnabled) {
                hapticGenerator = HapticGenerator.create(mediaPlayer.getAudioSessionId());
                hapticGenerator = injectables.createHapticGenerator(mediaPlayer);
                hapticGenerator.setEnabled(true);
            }
            VolumeShaper volumeShaper = null;
@@ -91,7 +96,7 @@ public class LocalRingtonePlayer
                volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
            }
            mediaPlayer.prepare();
            return new LocalRingtonePlayer(mediaPlayer, audioAttributes, audioManager,
            return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager,
                    hapticGenerator, volumeShaper);
        } catch (SecurityException | IOException e) {
            if (hapticGenerator != null) {
@@ -113,6 +118,7 @@ public class LocalRingtonePlayer
    static LocalRingtonePlayer createForFallback(
            @NonNull AudioManager audioManager, @NonNull AssetFileDescriptor afd,
            @NonNull AudioAttributes audioAttributes,
            @NonNull Ringtone.Injectables injectables,
            @Nullable VolumeShaper.Configuration volumeShaperConfig,
            @Nullable AudioDeviceInfo preferredDevice,
            boolean initialLooping, float initialVolume) {
@@ -122,7 +128,7 @@ public class LocalRingtonePlayer
        Objects.requireNonNull(audioAttributes);
        Trace.beginSection("createFallbackLocalMediaPlayer");

        MediaPlayer mediaPlayer = new MediaPlayer();
        MediaPlayer mediaPlayer = injectables.newMediaPlayer();
        try {
            if (afd.getDeclaredLength() < 0) {
                mediaPlayer.setDataSource(afd.getFileDescriptor());
@@ -140,7 +146,7 @@ public class LocalRingtonePlayer
                volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
            }
            mediaPlayer.prepare();
            return new LocalRingtonePlayer(mediaPlayer, audioAttributes, audioManager,
            return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager,
                    /* hapticGenerator= */ null, volumeShaper);
        } catch (SecurityException | IOException e) {
            Log.e(TAG, "Failed to open fallback ringtone");
@@ -202,7 +208,7 @@ public class LocalRingtonePlayer
    @Override
    public void setHapticGeneratorEnabled(boolean enabled) {
        if (enabled && mHapticGenerator == null) {
            mHapticGenerator = HapticGenerator.create(mMediaPlayer.getAudioSessionId());
            mHapticGenerator = mInjectables.createHapticGenerator(mMediaPlayer);
        }
        if (mHapticGenerator != null) {
            mHapticGenerator.setEnabled(enabled);
+330 −133

File changed.

Preview size limit exceeded, changes collapsed.

+31 −94
Original line number Diff line number Diff line
@@ -353,6 +353,25 @@ public class RingtoneManager {
        }
    }

    /** @hide */
    @NonNull
    public static AudioAttributes getDefaultAudioAttributes(int ringtoneType) {
        AudioAttributes.Builder builder = new AudioAttributes.Builder();
        switch (ringtoneType) {
            case TYPE_ALARM:
                builder.setUsage(AudioAttributes.USAGE_ALARM);
                break;
            case TYPE_NOTIFICATION:
                builder.setUsage(AudioAttributes.USAGE_NOTIFICATION);
                break;
            default:  // ringtone or all
                builder.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
                break;
        }
        builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
        return builder.build();
    }

    /**
     * Whether retrieving another {@link Ringtone} will stop playing the
     * previously retrieved {@link Ringtone}.
@@ -477,8 +496,10 @@ public class RingtoneManager {
            mPreviousRingtone.stop();
        }

        mPreviousRingtone =
                getRingtone(mContext, getRingtoneUri(position), inferStreamType(), true);
        mPreviousRingtone = new Ringtone.Builder(
                mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType))
                .setUri(getRingtoneUri(position))
                .build();
        return mPreviousRingtone;
    }

@@ -673,40 +694,9 @@ public class RingtoneManager {
     * @return A {@link Ringtone} for the given URI, or null.
     */
    public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
        // Don't set the stream type
        return getRingtone(context, ringtoneUri, -1, true);
    }

    /**
     * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI.
     * <p>
     * If the given URI cannot be opened for any reason, this method will
     * attempt to fallback on another sound. If it cannot find any, it will
     * return null.
     *
     * @param context A context used to query.
     * @param ringtoneUri The {@link Uri} of a sound or ringtone.
     * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
     * @return A {@link Ringtone} for the given URI, or null.
     *
     * @hide
     */
    public static Ringtone getRingtone(
            final Context context, Uri ringtoneUri,
            @Nullable VolumeShaper.Configuration volumeShaperConfig) {
        // Don't set the stream type
        return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, true);
    }

    /**
     * @hide
     */
    public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
            @Nullable VolumeShaper.Configuration volumeShaperConfig,
            boolean createLocalMediaPlayer) {
        // Don't set the stream type
        return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig,
                createLocalMediaPlayer);
        return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(-1))
                .setUri(ringtoneUri)
                .build();
    }

    /**
@@ -715,64 +705,11 @@ public class RingtoneManager {
    public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
            @Nullable VolumeShaper.Configuration volumeShaperConfig,
            AudioAttributes audioAttributes) {
        // Don't set the stream type
        Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
                volumeShaperConfig, false);
        if (ringtone != null) {
            ringtone.setAudioAttributesField(audioAttributes);
            if (!ringtone.reinitializeActivePlayer()) {
                Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
                return null;
            }
        }
        return ringtone;
    }

    //FIXME bypass the notion of stream types within the class
    /**
     * Returns a {@link Ringtone} for a given sound URI on the given stream
     * type. Normally, if you change the stream type on the returned
     * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
     * an optimized route to avoid that.
     *
     * @param streamType The stream type for the ringtone, or -1 if it should
     *            not be set (and the default used instead).
     * @param createLocalMediaPlayer when true, the ringtone returned will be fully
     *      created otherwise, it will require the caller to create the media player manually
     *      {@link Ringtone#reinitializeActivePlayer()} in order to play the Ringtone.
     * @see #getRingtone(Context, Uri)
     */
    @UnsupportedAppUsage
    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
            boolean createLocalMediaPlayer) {
        return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */,
                createLocalMediaPlayer);
    }

    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
            @Nullable VolumeShaper.Configuration volumeShaperConfig,
            boolean createLocalMediaPlayer) {
        try {
            final Ringtone r = new Ringtone(context, true);
            if (streamType >= 0) {
                //FIXME deprecated call
                r.setStreamType(streamType);
            }

            r.setVolumeShaperConfig(volumeShaperConfig);
            r.setUri(ringtoneUri, volumeShaperConfig);
            if (createLocalMediaPlayer) {
                if (!r.reinitializeActivePlayer()) {
                    Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
                    return null;
                }
            }
            return r;
        } catch (Exception ex) {
            Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
        }

        return null;
        // TODO: move caller(s) away from this method: inline the builder call.
        return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes)
                .setUri(ringtoneUri)
                .setVolumeShaperConfig(volumeShaperConfig)
                .build();
    }

    /**
+2 −0
Original line number Diff line number Diff line
@@ -20,7 +20,9 @@ android_test {
        "androidx.test.ext.junit",
        "androidx.test.rules",
        "android-ex-camera2",
        "testables",
        "testng",
        "truth-prebuilt",
    ],
    jni_libs: [
        "libdexmakerjvmtiagent",
+2 −0
Original line number Diff line number Diff line
# Haptics team also works on Ringtone
per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
Loading