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

Commit 428a66a7 authored by Raj Goparaju's avatar Raj Goparaju
Browse files

Adapt fade manager configurations

Use configurable fade manager configuration
instead of default. This allows clients to
configure various properties of fade cycle.

Also, support fade in using specific volume
shaper configuration, instead of using the
same fade out volume shaper configuration.

Bug: 186905459
Bug: 307354764
Test: atest -c FadeOutManager FadeConfigurations

Change-Id: I77b7b15f0384dec69079355adbeb5b3767d92bbb
parent 5db107c2
Loading
Loading
Loading
Loading
+317 −26
Original line number Diff line number Diff line
@@ -16,13 +16,21 @@

package com.android.server.audio;

import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioPlaybackConfiguration;
import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Class to encapsulate configurations used for fading players
@@ -69,51 +77,150 @@ public final class FadeConfigurations {

    private static final int INVALID_UID = -1;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private FadeManagerConfiguration mDefaultFadeManagerConfig;
    @GuardedBy("mLock")
    private FadeManagerConfiguration mUpdatedFadeManagerConfig;
    /** active fade manager is one of updated > default */
    @GuardedBy("mLock")
    private FadeManagerConfiguration mActiveFadeManagerConfig;

    /**
     * Sets the custom fade manager configuration
     *
     * @param fadeManagerConfig custom fade manager configuration
     * @return {@code true} if setting custom fade manager configuration succeeds or {@code false}
     * otherwise (example - when fade manager configuration is disabled)
     */
    public boolean setFadeManagerConfiguration(
            @NonNull FadeManagerConfiguration fadeManagerConfig) {
        if (!enableFadeManagerConfiguration()) {
            return false;
        }

        synchronized (mLock) {
            mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
                    "Fade manager configuration cannot be null");
            mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
        }
        return true;
    }

    /**
     * Clears the fade manager configuration that was previously set with
     * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
     *
     * @return {@code true} if previously set fade manager configuration is cleared or {@code false}
     * otherwise (say, when fade manager configuration is disabled)
     */
    public boolean clearFadeManagerConfiguration() {
        if (!enableFadeManagerConfiguration()) {
            return false;
        }
        synchronized (mLock) {
            mUpdatedFadeManagerConfig = null;
            mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
        }
        return true;
    }

    /**
     * Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to
     * fade
     *
     * @return list of {@link android.media.AudioAttributes.AttributeUsage}
     */
    @NonNull
    public List<Integer> getFadeableUsages() {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_FADEABLE_USAGES;
        }

        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // when fade is not enabled, return an empty list instead
            return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages()
                    : Collections.EMPTY_LIST;
        }
    }

    /**
     * Query {@link android.media.AudioAttributes.AttributeContentType content types} that are
     * exempted from fade enforcement
     *
     * @return list of {@link android.media.AudioAttributes.AttributeContentType}
     */
    @NonNull
    public List<Integer> getUnfadeableContentTypes() {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_UNFADEABLE_CONTENT_TYPES;
        }

        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // when fade is not enabled, return an empty list instead
            return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes()
                    : Collections.EMPTY_LIST;
        }
    }

    /**
     * Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are
     * exempted from fade enforcement
     *
     * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
     */
    @NonNull
    public List<Integer> getUnfadeablePlayerTypes() {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_UNFADEABLE_PLAYER_TYPES;
        }

        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // when fade is not enabled, return an empty list instead
            return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes()
                    : Collections.EMPTY_LIST;
        }
    }

    /**
     * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied
     * for the fade-out
     *
     * @param aa The {@link android.media.AudioAttributes}
     * @return {@link android.media.VolumeShaper.Configuration} for the
     * {@link android.media.AudioAttributes.AttributeUsage} or default volume shaper if not
     * configured
     * {@link android.media.AudioAttributes} or default volume shaper if not configured
     */
    @NonNull
    public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_FADEOUT_VSHAPE;
        }
        return getOptimalFadeOutVolShaperConfig(aa);
    }

    /**
     * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the
     * fade in
     *
     * @param aa The {@link android.media.AudioAttributes}
     * @return {@link android.media.VolumeShaper.Configuration} for the
     * {@link android.media.AudioAttributes} or {@code null} otherwise
     */
    @Nullable
    public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) {
        if (!enableFadeManagerConfiguration()) {
            return null;
        }
        return getOptimalFadeInVolShaperConfig(aa);
    }


    /**
     * Get the duration to fade out a player of type usage
     *
     * @param aa The {@link android.media.AudioAttributes}
     * @return duration in milliseconds for the
     * {@link android.media.AudioAttributes} or default duration if not configured
@@ -122,22 +229,73 @@ public final class FadeConfigurations {
        if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) {
            return 0;
        }
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_FADE_OUT_DURATION_MS;
        }
        return getOptimalFadeOutDuration(aa);
    }

    /**
     * Get the delay to fade in offending players that do not stop after losing audio focus.
     * Get the delay to fade in offending players that do not stop after losing audio focus
     *
     * @param aa The {@link android.media.AudioAttributes}
     * @return delay in milliseconds for the
     * {@link android.media.AudioAttributes.Attribute} or default delay if not configured
     */
    public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
        }

        synchronized (mLock) {
            return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders();
        }
    }

    /**
     * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement
     *
     * @return list of {@link android.media.AudioAttributes}
     */
    @NonNull
    public List<AudioAttributes> getUnfadeableAudioAttributes() {
        // unfadeable audio attributes is only supported with fade manager configurations
        if (!enableFadeManagerConfiguration()) {
            return Collections.EMPTY_LIST;
        }

        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // when fade is not enabled, return empty list
            return fadeManagerConfig.isFadeEnabled()
                    ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST;
        }
    }

    /**
     * Query uids that are exempted from fade enforcement
     *
     * @return list of uids
     */
    @NonNull
    public List<Integer> getUnfadeableUids() {
        // unfadeable uids is only supported with fade manager configurations
        if (!enableFadeManagerConfiguration()) {
            return Collections.EMPTY_LIST;
        }

        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // when fade is not enabled, return empty list
            return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids()
                    : Collections.EMPTY_LIST;
        }
    }

    /**
     * Check if it is allowed to fade for the given {@link android.media.AudioAttributes},
     * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config.
     * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config
     *
     * @param aa The {@link android.media.AudioAttributes}
     * @param uid The uid of the client owning the player
     * @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType}
@@ -145,36 +303,169 @@ public final class FadeConfigurations {
     */
    public boolean isFadeable(@NonNull AudioAttributes aa, int uid,
            @AudioPlaybackConfiguration.PlayerType int playerType) {
        if (isPlayerTypeUnfadeable(playerType)) {
        synchronized (mLock) {
            if (isPlayerTypeUnfadeableLocked(playerType)) {
                if (DEBUG) {
                    Slog.i(TAG, "not fadeable: player type:" + playerType);
                }
                return false;
            }
        if (isContentTypeUnfadeable(aa.getContentType())) {
            if (isContentTypeUnfadeableLocked(aa.getContentType())) {
                if (DEBUG) {
                    Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
                }
                return false;
            }
        if (!isUsageFadeable(aa.getUsage())) {
            if (!isUsageFadeableLocked(aa.getSystemUsage())) {
                if (DEBUG) {
                    Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
                }
                return false;
            }
            // new configs using fade manager configuration
            if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) {
                return false;
            }
            return true;
        }
    }

    /** Tries to get the fade out volume shaper config closest to the audio attributes */
    private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) {
        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // check if the specific audio attributes has a volume shaper config defined
            VolumeShaper.Configuration volShaperConfig =
                    fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa);
            if (volShaperConfig != null) {
                return volShaperConfig;
            }

            // get the volume shaper config for usage
            // for fadeable usages, this should never return null
            return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage(
                    aa.getSystemUsage());
        }
    }

    /** Tries to get the fade in volume shaper config closest to the audio attributes */
    private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) {
        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // check if the specific audio attributes has a volume shaper config defined
            VolumeShaper.Configuration volShaperConfig =
                    fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa);
            if (volShaperConfig != null) {
                return volShaperConfig;
            }

            // get the volume shaper config for usage
            // for fadeable usages, this should never return null
            return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage());
        }
    }

    /** Tries to get the duration closest to the audio attributes */
    private long getOptimalFadeOutDuration(AudioAttributes aa) {
        synchronized (mLock) {
            FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
            // check if specific audio attributes has a duration defined
            long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa);
            if (duration != FadeManagerConfiguration.DURATION_NOT_SET) {
                return duration;
            }

            // get the duration for usage
            // for fadeable usages, this should never return DURATION_NOT_SET
            return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage());
        }
    }

    @GuardedBy("mLock")
    private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) {
        if (isAudioAttributesUnfadeableLocked(aa)) {
            if (DEBUG) {
                Slog.i(TAG, "not fadeable: aa:" + aa);
            }
            return true;
        }
        if (isUidUnfadeableLocked(uid)) {
            if (DEBUG) {
                Slog.i(TAG, "not fadeable: uid:" + uid);
            }
            return true;
        }
        return false;
    }

    @GuardedBy("mLock")
    private boolean isUsageFadeableLocked(int usage) {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_FADEABLE_USAGES.contains(usage);
        }
        return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage);
    }

    @GuardedBy("mLock")
    private boolean isContentTypeUnfadeableLocked(int contentType) {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType);
        }
        return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType);
    }

    private boolean isUsageFadeable(int usage) {
        return getFadeableUsages().contains(usage);
    @GuardedBy("mLock")
    private boolean isPlayerTypeUnfadeableLocked(int playerType) {
        if (!enableFadeManagerConfiguration()) {
            return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType);
        }
        return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType);
    }

    @GuardedBy("mLock")
    private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) {
        if (!enableFadeManagerConfiguration()) {
            // default fade configs do not support unfadeable audio attributes, hence return false
            return false;
        }
        return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa);
    }

    private boolean isContentTypeUnfadeable(int contentType) {
        return getUnfadeableContentTypes().contains(contentType);
    @GuardedBy("mLock")
    private boolean isUidUnfadeableLocked(int uid) {
        if (!enableFadeManagerConfiguration()) {
            // default fade configs do not support unfadeable uids, hence return false
            return false;
        }
        return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid);
    }

    private boolean isPlayerTypeUnfadeable(int playerType) {
        return getUnfadeablePlayerTypes().contains(playerType);
    @GuardedBy("mLock")
    private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() {
        if (mActiveFadeManagerConfig == null) {
            mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
        }
        return mActiveFadeManagerConfig;
    }

    /** Priority between fade manager configs: Updated > Default */
    @GuardedBy("mLock")
    private FadeManagerConfiguration getActiveFadeMgrConfigLocked() {
        // below configs are arranged in the order of priority
        // configs placed higher have higher priority
        if (mUpdatedFadeManagerConfig != null) {
            return mUpdatedFadeManagerConfig;
        }

        // default - must be the lowest priority
        return getDefaultFadeManagerConfigLocked();
    }

    @GuardedBy("mLock")
    private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() {
        if (mDefaultFadeManagerConfig == null) {
            mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build();
        }
        return mDefaultFadeManagerConfig;
    }
}
+124 −54

File changed.

Preview size limit exceeded, changes collapsed.

+33 −3
Original line number Diff line number Diff line
@@ -156,8 +156,7 @@ public final class PlaybackActivityMonitor
    private final int mMaxAlarmVolume;
    private int mPrivilegedAlarmActiveCount = 0;
    private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
    private final FadeOutManager mFadeOutManager;

    private final FadeOutManager mFadeOutManager = new FadeOutManager();

    PlaybackActivityMonitor(Context context, int maxAlarmVolume,
            Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
@@ -167,7 +166,6 @@ public final class PlaybackActivityMonitor
        AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
        mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
        initEventHandler();
        mFadeOutManager = new FadeOutManager(new FadeConfigurations());
    }

    //=================================================================
@@ -1337,6 +1335,38 @@ public final class PlaybackActivityMonitor
        }
    }

    static final class FadeEvent extends EventLogger.Event {
        private final int mPlayerIId;
        private final int mPlayerType;
        private final int mClientUid;
        private final int mClientPid;
        private final AudioAttributes mPlayerAttr;
        private final VolumeShaper.Configuration mVShaper;
        private final VolumeShaper.Operation mVOperation;

        FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
                VolumeShaper.Operation vOperation) {
            mPlayerIId = apc.getPlayerInterfaceId();
            mClientUid = apc.getClientUid();
            mClientPid = apc.getClientPid();
            mPlayerAttr = apc.getAudioAttributes();
            mPlayerType = apc.getPlayerType();
            mVShaper = vShaper;
            mVOperation = vOperation;
        }

        @Override
        public String eventToString() {
            return "Fade Event:" + " player piid:" + mPlayerIId
                    + " uid/pid:" + mClientUid + "/" + mClientPid
                    + " player type:"
                    + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
                    + " attr:" + mPlayerAttr
                    + " volume shaper:" + mVShaper
                    + " volume operation:" + mVOperation;
        }
    }

    private abstract static class VolumeShaperEvent extends EventLogger.Event {
        private final int mPlayerIId;
        private final boolean mSkipRamp;
+130 −5

File changed.

Preview size limit exceeded, changes collapsed.

+1 −12
Original line number Diff line number Diff line
@@ -23,8 +23,6 @@ import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;

import static org.junit.Assert.assertThrows;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -75,19 +73,10 @@ public final class FadeOutManagerTest {

    @Before
    public void setUp() {
        mFadeOutManager = new FadeOutManager(new FadeConfigurations());
        mFadeOutManager = new FadeOutManager();
        mContext = ApplicationProvider.getApplicationContext();
    }

    @Test
    public void constructor_nullFadeConfigurations_fails() {
        Throwable thrown = assertThrows(NullPointerException.class, () -> new FadeOutManager(
                /* FadeConfigurations= */ null));

        expect.withMessage("Constructor exception")
                .that(thrown).hasMessageThat().contains("Fade configurations can not be null");
    }

    @Test
    public void testCanCauseFadeOut_forFaders_returnsTrue() {
        FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client",