diff options
5 files changed, 605 insertions, 100 deletions
diff --git a/services/core/java/com/android/server/audio/FadeConfigurations.java b/services/core/java/com/android/server/audio/FadeConfigurations.java index 2e27c7697e82..3dc723045fa8 100644 --- a/services/core/java/com/android/server/audio/FadeConfigurations.java +++ b/services/core/java/com/android/server/audio/FadeConfigurations.java @@ -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() { - return DEFAULT_FADEABLE_USAGES; + 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() { - return DEFAULT_UNFADEABLE_CONTENT_TYPES; + 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() { - return DEFAULT_UNFADEABLE_PLAYER_TYPES; + 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) { - return DEFAULT_FADEOUT_VSHAPE; + 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; } - return DEFAULT_FADE_OUT_DURATION_MS; + 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) { - return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS; + 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)) { - if (DEBUG) { - Slog.i(TAG, "not fadeable: player type:" + playerType); + synchronized (mLock) { + if (isPlayerTypeUnfadeableLocked(playerType)) { + if (DEBUG) { + Slog.i(TAG, "not fadeable: player type:" + playerType); + } + return false; } - return false; + if (isContentTypeUnfadeableLocked(aa.getContentType())) { + if (DEBUG) { + Slog.i(TAG, "not fadeable: content type:" + aa.getContentType()); + } + return false; + } + 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()); } - if (isContentTypeUnfadeable(aa.getContentType())) { + } + + /** 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: content type:" + aa.getContentType()); + Slog.i(TAG, "not fadeable: aa:" + aa); } - return false; + return true; } - if (!isUsageFadeable(aa.getUsage())) { + if (isUidUnfadeableLocked(uid)) { if (DEBUG) { - Slog.i(TAG, "not fadeable: usage:" + aa.getUsage()); + 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); + } + + @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 true; + return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa); + } + + @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 isUsageFadeable(int usage) { - return getFadeableUsages().contains(usage); + @GuardedBy("mLock") + private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() { + if (mActiveFadeManagerConfig == null) { + mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked(); + } + return mActiveFadeManagerConfig; } - private boolean isContentTypeUnfadeable(int contentType) { - return getUnfadeableContentTypes().contains(contentType); + /** 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(); } - private boolean isPlayerTypeUnfadeable(int playerType) { - return getUnfadeablePlayerTypes().contains(playerType); + @GuardedBy("mLock") + private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() { + if (mDefaultFadeManagerConfig == null) { + mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build(); + } + return mDefaultFadeManagerConfig; } } diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java index 1171f97533c7..a0f9eda8e2b4 100644 --- a/services/core/java/com/android/server/audio/FadeOutManager.java +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -16,21 +16,24 @@ 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.AudioManager; import android.media.AudioPlaybackConfiguration; +import android.media.FadeManagerConfiguration; import android.media.VolumeShaper; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Objects; +import java.util.List; +import java.util.Map; /** * Class to handle fading out players @@ -40,14 +43,6 @@ public final class FadeOutManager { public static final String TAG = "AS.FadeOutManager"; private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG; - private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = - new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) - .createIfNeeded() - .build(); - - // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp - private static final VolumeShaper.Operation PLAY_SKIP_RAMP = - new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); private final Object mLock = new Object(); @@ -57,16 +52,40 @@ public final class FadeOutManager { @GuardedBy("mLock") private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>(); - @GuardedBy("mLock") - private FadeConfigurations mFadeConfigurations; + private final FadeConfigurations mFadeConfigurations = new FadeConfigurations(); + + /** + * Sets the custom fade manager configuration to be used for player fade out and in + * + * @param fadeManagerConfig custom fade manager configuration + * @return {@code true} if setting fade manager config succeeded, {@code false} otherwise + */ + boolean setFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) { + if (!enableFadeManagerConfiguration()) { + return false; + } - public FadeOutManager() { - mFadeConfigurations = new FadeConfigurations(); + // locked to ensure the fade configs are not updated while faded app state is being updated + synchronized (mLock) { + return mFadeConfigurations.setFadeManagerConfiguration(fadeManagerConfig); + } } - public FadeOutManager(FadeConfigurations fadeConfigurations) { - mFadeConfigurations = Objects.requireNonNull(fadeConfigurations, - "Fade configurations can not be null"); + /** + * Clears the fade manager configuration that was previously set with + * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)} + * + * @return {@code true} if clearing fade manager config succeeded, {@code false} otherwise + */ + boolean clearFadeManagerConfiguration() { + if (!enableFadeManagerConfiguration()) { + return false; + } + + // locked to ensure the fade configs are not updated while faded app state is being updated + synchronized (mLock) { + return mFadeConfigurations.clearFadeManagerConfiguration(); + } } // TODO explore whether a shorter fade out would be a better UX instead of not fading out at all @@ -128,7 +147,7 @@ public final class FadeOutManager { } } - void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) { + void fadeOutUid(int uid, List<AudioPlaybackConfiguration> players) { Slog.i(TAG, "fadeOutUid() uid:" + uid); synchronized (mLock) { if (!mUidToFadedAppsMap.contains(uid)) { @@ -148,15 +167,31 @@ public final class FadeOutManager { * @param uid the uid for the app to unfade out * @param players map of current available players (so we can get an APC from piid) */ - void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) { + void unfadeOutUid(int uid, Map<Integer, AudioPlaybackConfiguration> players) { Slog.i(TAG, "unfadeOutUid() uid:" + uid); synchronized (mLock) { - final FadedOutApp fa = mUidToFadedAppsMap.get(uid); + FadedOutApp fa = mUidToFadedAppsMap.get(uid); if (fa == null) { return; } mUidToFadedAppsMap.remove(uid); - fa.removeUnfadeAll(players); + + if (!enableFadeManagerConfiguration()) { + fa.removeUnfadeAll(players); + return; + } + + // since fade manager configs may have volume-shaper config per audio attributes, + // iterate through each palyer and gather respective configs for fade in + ArrayList<AudioPlaybackConfiguration> apcs = new ArrayList<>(players.values()); + for (int index = 0; index < apcs.size(); index++) { + AudioPlaybackConfiguration apc = apcs.get(index); + VolumeShaper.Configuration config = mFadeConfigurations + .getFadeInVolumeShaperConfig(apc.getAudioAttributes()); + fa.fadeInPlayer(apc, config); + } + // ideal case all players should be faded in + fa.clear(); } } @@ -209,16 +244,6 @@ public final class FadeOutManager { } } - /** - * Update fade configurations used for player fade operations - * @param fadeConfigurations set of configs that define fade properties - */ - void setFadeConfigurations(@NonNull FadeConfigurations fadeConfigurations) { - synchronized (mLock) { - mFadeConfigurations = fadeConfigurations; - } - } - void dump(PrintWriter pw) { synchronized (mLock) { for (int index = 0; index < mUidToFadedAppsMap.size(); index++) { @@ -232,6 +257,15 @@ public final class FadeOutManager { * Class to group players from a common app, that are faded out. */ private static final class FadedOutApp { + private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = + new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) + .createIfNeeded() + .build(); + + // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp + private static final VolumeShaper.Operation PLAY_SKIP_RAMP = + new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build(); + private final int mUid; // key -> piid; value -> volume shaper config applied private final SparseArray<VolumeShaper.Configuration> mFadedPlayers = new SparseArray<>(); @@ -269,17 +303,8 @@ public final class FadeOutManager { return; } if (apc.getPlayerProxy() != null) { - try { - PlaybackActivityMonitor.sEventLogger.enqueue( - (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog( - TAG)); - apc.getPlayerProxy().applyVolumeShaper(volShaper, - skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); - mFadedPlayers.put(piid, volShaper); - } catch (Exception e) { - Slog.e(TAG, "Error fading out player piid:" + piid - + " uid:" + apc.getClientUid(), e); - } + applyVolumeShaperInternal(apc, piid, volShaper, + skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); } else { if (DEBUG) { Slog.v(TAG, "Error fading out player piid:" + piid @@ -288,21 +313,13 @@ public final class FadeOutManager { } } - void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) { + void removeUnfadeAll(Map<Integer, AudioPlaybackConfiguration> players) { for (int index = 0; index < mFadedPlayers.size(); index++) { int piid = mFadedPlayers.keyAt(index); final AudioPlaybackConfiguration apc = players.get(piid); if ((apc != null) && (apc.getPlayerProxy() != null)) { - final VolumeShaper.Configuration volShaper = mFadedPlayers.valueAt(index); - try { - PlaybackActivityMonitor.sEventLogger.enqueue( - (new EventLogger.StringEvent("unfading out piid:" - + piid)).printLog(TAG)); - apc.getPlayerProxy().applyVolumeShaper(volShaper, - VolumeShaper.Operation.REVERSE); - } catch (Exception e) { - Slog.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e); - } + applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null, + VolumeShaper.Operation.REVERSE); } else { // this piid was in the list of faded players, but wasn't found if (DEBUG) { @@ -314,8 +331,61 @@ public final class FadeOutManager { mFadedPlayers.clear(); } + void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc, + @Nullable VolumeShaper.Configuration config) { + int piid = Integer.valueOf(apc.getPlayerInterfaceId()); + // if not found, no need to fade in since it was never faded out + if (!mFadedPlayers.contains(piid)) { + if (DEBUG) { + Slog.v(TAG, "Player (piid: " + piid + ") for uid (" + mUid + + ") is not faded out, no need to fade in"); + } + return; + } + + mFadedPlayers.remove(piid); + if (apc.getPlayerProxy() != null) { + applyVolumeShaperInternal(apc, piid, config, + config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE); + } else { + if (DEBUG) { + Slog.v(TAG, "Error fading in player piid:" + piid + + ", player not found for uid " + mUid); + } + } + } + + void clear() { + if (mFadedPlayers.size() > 0) { + if (DEBUG) { + Slog.v(TAG, "Non empty faded players list being cleared! Faded out players:" + + mFadedPlayers); + } + } + // should the players be faded in irrespective? + mFadedPlayers.clear(); + } + void removeReleased(@NonNull AudioPlaybackConfiguration apc) { mFadedPlayers.delete(Integer.valueOf(apc.getPlayerInterfaceId())); } + + private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid, + VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) { + VolumeShaper.Configuration config = volShaperConfig; + // when operation is reverse, use the fade out volume shaper config instead + if (operation.equals(VolumeShaper.Operation.REVERSE)) { + config = mFadedPlayers.get(piid); + } + try { + PlaybackActivityMonitor.sEventLogger.enqueue( + (new PlaybackActivityMonitor.FadeEvent(apc, config, operation)) + .printLog(TAG)); + apc.getPlayerProxy().applyVolumeShaper(config, operation); + } catch (Exception e) { + Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid + + " operation:" + operation, e); + } + } } } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index bc9b9b4b1c88..1358a9b5d9a5 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -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; diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java index 6fca56134393..69817ad5035a 100644 --- a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java @@ -16,17 +16,21 @@ package com.android.server.audio; -import static android.media.AudioAttributes.USAGE_MEDIA; -import static android.media.AudioAttributes.USAGE_GAME; -import static android.media.AudioAttributes.USAGE_ASSISTANT; import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH; +import static android.media.AudioAttributes.USAGE_ASSISTANT; +import static android.media.AudioAttributes.USAGE_EMERGENCY; +import static android.media.AudioAttributes.USAGE_GAME; +import static android.media.AudioAttributes.USAGE_MEDIA; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK; import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN; +import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; import android.media.AudioAttributes; +import android.media.FadeManagerConfiguration; import android.media.VolumeShaper; +import android.platform.test.flag.junit.SetFlagsRule; import com.google.common.truth.Expect; @@ -42,6 +46,7 @@ import java.util.List; public final class FadeConfigurationsTest { private FadeConfigurations mFadeConfigurations; private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000; + private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000; private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000; private static final long DURATION_FOR_UNFADEABLE_MS = 0; private static final int TEST_UID_SYSTEM = 1000; @@ -60,11 +65,19 @@ public final class FadeConfigurationsTest { private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE = new VolumeShaper.Configuration.Builder() .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID) - .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f} , - /* volumes= */new float[]{1.f, 0.65f, 0.0f}) + .setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f}, + /* volumes= */ new float[]{1.f, 0.65f, 0.0f}) .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) .setDuration(DEFAULT_FADE_OUT_DURATION_MS) .build(); + private static final VolumeShaper.Configuration DEFAULT_FADEIN_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID) + .setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f}, + /* volumes= */ new float[]{0.f, 0.30f, 1.0f}) + .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) + .setDuration(DEFAULT_FADE_IN_DURATION_MS) + .build(); private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE = new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build(); @@ -72,12 +85,18 @@ public final class FadeConfigurationsTest { new AudioAttributes.Builder().setUsage(USAGE_GAME).build(); private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE = new AudioAttributes.Builder().setUsage(USAGE_ASSISTANT).build(); + private static final AudioAttributes TEST_EMERGENCY_AUDIO_ATTRIBUTE = + new AudioAttributes.Builder().setSystemUsage(USAGE_EMERGENCY).build(); + private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE = new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build(); @Rule public final Expect expect = Expect.create(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() { mFadeConfigurations = new FadeConfigurations(); @@ -156,4 +175,110 @@ public final class FadeConfigurationsTest { .that(mFadeConfigurations.isFadeable(TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER, PLAYER_TYPE_AAUDIO)).isFalse(); } + + @Test + public void testGetFadeableUsages_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<Integer> usageList = List.of(AudioAttributes.USAGE_ALARM, + AudioAttributes.USAGE_EMERGENCY); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ usageList, + /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null, + /* unfadeableAudioAttrs= */ null); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Fadeable usages with fade manager configuration") + .that(fadeConfigs.getFadeableUsages()).isEqualTo(fmc.getFadeableUsages()); + } + + @Test + public void testGetUnfadeableContentTypes_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<Integer> contentTypesList = List.of(AudioAttributes.CONTENT_TYPE_MUSIC, + AudioAttributes.CONTENT_TYPE_MOVIE); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null, + /* unfadeableContentTypes= */ contentTypesList, /* unfadeableUids= */ null, + /* unfadeableAudioAttrs= */ null); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Unfadeable content types with fade manager configuration") + .that(fadeConfigs.getUnfadeableContentTypes()) + .isEqualTo(fmc.getUnfadeableContentTypes()); + } + + @Test + public void testGetUnfadeableAudioAttributes_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<AudioAttributes> attrsList = List.of(TEST_ASSISTANT_AUDIO_ATTRIBUTE, + TEST_EMERGENCY_AUDIO_ATTRIBUTE); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null, + /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null, + /* unfadeableAudioAttrs= */ attrsList); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Unfadeable audio attributes with fade manager configuration") + .that(fadeConfigs.getUnfadeableAudioAttributes()) + .isEqualTo(fmc.getUnfadeableAudioAttributes()); + } + + @Test + public void testGetUnfadeableUids_withFadeManagerConfigurations_equals() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION); + List<Integer> uidsList = List.of(TEST_UID_SYSTEM, TEST_UID_USER); + FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null, + /* unfadeableContentTypes= */ null, /* unfadeableUids= */ uidsList, + /* unfadeableAudioAttrs= */ null); + FadeConfigurations fadeConfigs = new FadeConfigurations(); + + fadeConfigs.setFadeManagerConfiguration(fmc); + + expect.withMessage("Unfadeable uids with fade manager configuration") + .that(fadeConfigs.getUnfadeableUids()).isEqualTo(fmc.getUnfadeableUids()); + } + + private static FadeManagerConfiguration createFadeMgrConfig(List<Integer> fadeableUsages, + List<Integer> unfadeableContentTypes, List<Integer> unfadeableUids, + List<AudioAttributes> unfadeableAudioAttrs) { + FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder(); + if (fadeableUsages != null) { + builder.setFadeableUsages(fadeableUsages); + } + if (unfadeableContentTypes != null) { + builder.setUnfadeableContentTypes(unfadeableContentTypes); + } + if (unfadeableUids != null) { + builder.setUnfadeableUids(unfadeableUids); + } + if (unfadeableAudioAttrs != null) { + builder.setUnfadeableAudioAttributes(unfadeableAudioAttrs); + } + if (fadeableUsages != null) { + for (int index = 0; index < fadeableUsages.size(); index++) { + builder.setFadeOutVolumeShaperConfigForAudioAttributes( + createGenericAudioAttributesForUsage(fadeableUsages.get(index)), + DEFAULT_FADEOUT_VSHAPE); + } + } + if (fadeableUsages != null) { + for (int index = 0; index < fadeableUsages.size(); index++) { + builder.setFadeInVolumeShaperConfigForAudioAttributes( + createGenericAudioAttributesForUsage(fadeableUsages.get(index)), + DEFAULT_FADEIN_VSHAPE); + } + } + + return builder.build(); + } + + private static AudioAttributes createGenericAudioAttributesForUsage(int usage) { + if (AudioAttributes.isSystemUsage(usage)) { + return new AudioAttributes.Builder().setSystemUsage(usage).build(); + } + return new AudioAttributes.Builder().setUsage(usage).build(); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java index 65059d5ca8fd..fa94821d4ff2 100644 --- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java @@ -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,20 +73,11 @@ 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", "unit-test", TEST_UID_USER, |