script-astra/Android/Sdk/sources/android-35/android/media/FadeManagerConfiguration.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

1693 lines
73 KiB
Java

/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Class to encapsulate fade configurations.
*
* <p>Configurations are provided through:
* <ul>
* <li>Fadeable list: a positive list of fadeable type - usage</li>
* <li>Unfadeable lists: negative list of unfadeable types - content type, uid, audio attributes
* </li>
* <li>Volume shaper configs: fade in and fade out configs per usage or audio attributes
* </li>
* </ul>
*
* <p>Fade manager configuration can be created in one of the following ways:
* <ul>
* <li>Disabled fades:
* <pre class="prettyprint">
* new FadeManagerConfiguration.Builder()
* .setFadeState(FADE_STATE_DISABLED).build()
* </pre>
* Can be used to disable fading</li>
* <li>Default configurations including default fade duration:
* <pre class="prettyprint">
* new FadeManagerConfiguration.Builder()
* .setFadeState(FADE_STATE_ENABLED_DEFAULT).build()
* </pre>
* Can be used to enable default fading configurations</li>
* <li>Default configurations with custom fade duration:
* <pre class="prettyprint">
* new FadeManagerConfiguration.Builder(fade out duration, fade in duration)
* .setFadeState(FADE_STATE_ENABLED_DEFAULT).build()
* </pre>
* Can be used to enable default fadeability lists with configurable fade in and out duration
* </li>
* <li>Custom configurations and fade volume shapers:
* <pre class="prettyprint">
* new FadeManagerConfiguration.Builder(fade out duration, fade in duration)
* .setFadeState(FADE_STATE_ENABLED_DEFAULT)
* .setFadeableUsages(list of usages)
* .setUnfadeableContentTypes(list of content types)
* .setUnfadeableUids(list of uids)
* .setUnfadeableAudioAttributes(list of audio attributes)
* .setFadeOutVolumeShaperConfigForAudioAttributes(attributes, volume shaper config)
* .setFadeInDurationForUsaeg(usage, duration)
* ....
* .build() </pre>
* Achieves full customization of fadeability lists and configurations</li>
* <li>Also provides a copy constructor from another instance of fade manager configuration
* <pre class="prettyprint">
* new FadeManagerConfiguration.Builder(fadeManagerConfiguration)
* .addFadeableUsage(new usage)
* ....
* .build()</pre>
* Helps with recreating a new instance from another to simply change/add on top of the
* existing ones</li>
* </ul>
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfiguration implements Parcelable {
public static final String TAG = "FadeManagerConfiguration";
/**
* Defines the disabled fade state. No player will be faded in this state.
*/
public static final int FADE_STATE_DISABLED = 0;
/**
* Defines the enabled fade state with default configurations
*/
public static final int FADE_STATE_ENABLED_DEFAULT = 1;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FADE_STATE", value = {
FADE_STATE_DISABLED,
FADE_STATE_ENABLED_DEFAULT,
})
public @interface FadeStateEnum {}
/**
* Defines ID to be used in volume shaper for fading
*/
public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2;
/**
* Used to reset duration or return duration when not set
*
* @see Builder#setFadeOutDurationForUsage(int, long)
* @see Builder#setFadeInDurationForUsage(int, long)
* @see Builder#setFadeOutDurationForAudioAttributes(AudioAttributes, long)
* @see Builder#setFadeInDurationForAudioAttributes(AudioAttributes, long)
* @see #getFadeOutDurationForUsage(int)
* @see #getFadeInDurationForUsage(int)
* @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
* @see #getFadeInDurationForAudioAttributes(AudioAttributes)
*/
public static final @DurationMillisLong long DURATION_NOT_SET = 0;
/** Defines the default fade out duration */
private static final @DurationMillisLong long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
/** Defines the default fade in duration */
private static final @DurationMillisLong long DEFAULT_FADE_IN_DURATION_MS = 1_000;
/** Map of Usage to Fade volume shaper configs wrapper */
private final SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap;
/** Map of AudioAttributes to Fade volume shaper configs wrapper */
private final ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap;
/** list of fadeable usages */
private final @NonNull IntArray mFadeableUsages;
/** list of unfadeable content types */
private final @NonNull IntArray mUnfadeableContentTypes;
/** list of unfadeable player types */
private final @NonNull IntArray mUnfadeablePlayerTypes;
/** list of unfadeable uid(s) */
private final @NonNull IntArray mUnfadeableUids;
/** list of unfadeable AudioAttributes */
private final @NonNull List<AudioAttributes> mUnfadeableAudioAttributes;
/** fade state */
private final @FadeStateEnum int mFadeState;
/** fade out duration from builder - used for creating default fade out volume shaper */
private final @DurationMillisLong long mFadeOutDurationMillis;
/** fade in duration from builder - used for creating default fade in volume shaper */
private final @DurationMillisLong long mFadeInDurationMillis;
/** delay after which the offending players are faded back in */
private final @DurationMillisLong long mFadeInDelayForOffendersMillis;
private FadeManagerConfiguration(int fadeState, @DurationMillisLong long fadeOutDurationMillis,
@DurationMillisLong long fadeInDurationMillis,
@DurationMillisLong long offendersFadeInDelayMillis,
@NonNull SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap,
@NonNull ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap,
@NonNull IntArray fadeableUsages, @NonNull IntArray unfadeableContentTypes,
@NonNull IntArray unfadeablePlayerTypes, @NonNull IntArray unfadeableUids,
@NonNull List<AudioAttributes> unfadeableAudioAttributes) {
mFadeState = fadeState;
mFadeOutDurationMillis = fadeOutDurationMillis;
mFadeInDurationMillis = fadeInDurationMillis;
mFadeInDelayForOffendersMillis = offendersFadeInDelayMillis;
mUsageToFadeWrapperMap = Objects.requireNonNull(usageToFadeWrapperMap,
"Usage to fade wrapper map cannot be null");
mAttrToFadeWrapperMap = Objects.requireNonNull(attrToFadeWrapperMap,
"Attribute to fade wrapper map cannot be null");
mFadeableUsages = Objects.requireNonNull(fadeableUsages,
"List of fadeable usages cannot be null");
mUnfadeableContentTypes = Objects.requireNonNull(unfadeableContentTypes,
"List of unfadeable content types cannot be null");
mUnfadeablePlayerTypes = Objects.requireNonNull(unfadeablePlayerTypes,
"List of unfadeable player types cannot be null");
mUnfadeableUids = Objects.requireNonNull(unfadeableUids,
"List of unfadeable uids cannot be null");
mUnfadeableAudioAttributes = Objects.requireNonNull(unfadeableAudioAttributes,
"List of unfadeable audio attributes cannot be null");
}
/**
* Get the fade state
*/
@FadeStateEnum
public int getFadeState() {
return mFadeState;
}
/**
* Get the list of usages that can be faded
*
* @return list of {@link android.media.AudioAttributes usages} that shall be faded
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
public List<Integer> getFadeableUsages() {
ensureFadingIsEnabled();
return convertIntArrayToIntegerList(mFadeableUsages);
}
/**
* Get the list of {@link android.media.AudioPlaybackConfiguration player types} that can be
* faded
*
* @return list of {@link android.media.AudioPlaybackConfiguration player types}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
public List<Integer> getUnfadeablePlayerTypes() {
ensureFadingIsEnabled();
return convertIntArrayToIntegerList(mUnfadeablePlayerTypes);
}
/**
* Get the list of {@link android.media.AudioAttributes content types} that can be faded
*
* @return list of {@link android.media.AudioAttributes content types}
* @throws IllegalStateExceptionif if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
public List<Integer> getUnfadeableContentTypes() {
ensureFadingIsEnabled();
return convertIntArrayToIntegerList(mUnfadeableContentTypes);
}
/**
* Get the list of uids that cannot be faded
*
* @return list of uids that shall not be faded
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
public List<Integer> getUnfadeableUids() {
ensureFadingIsEnabled();
return convertIntArrayToIntegerList(mUnfadeableUids);
}
/**
* Get the list of {@link android.media.AudioAttributes} that cannot be faded
*
* @return list of {@link android.media.AudioAttributes} that shall not be faded
* @throws IllegalStateException if fade state is set to {@link #FADE_STATE_DISABLED}
*/
@NonNull
public List<AudioAttributes> getUnfadeableAudioAttributes() {
ensureFadingIsEnabled();
return mUnfadeableAudioAttributes;
}
/**
* Get the duration used to fade out players with {@link android.media.AudioAttributes usage}
*
* @param usage the {@link android.media.AudioAttributes usage}
* @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@IntRange(from = 0) @DurationMillisLong
public long getFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
mUsageToFadeWrapperMap.get(usage), /* isFadeIn= */ false));
}
/**
* Get the duration used to fade in players with {@link android.media.AudioAttributes usage}
*
* @param usage the {@link android.media.AudioAttributes usage}
* @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@IntRange(from = 0) @DurationMillisLong
public long getFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
mUsageToFadeWrapperMap.get(usage), /* isFadeIn= */ true));
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
* {@link android.media.AudioAttributes usage}
*
* @param usage the {@link android.media.AudioAttributes usage}
* @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
* {@code null} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@Nullable
public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(
@AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
/* isFadeIn= */ false);
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} used to fade in players with
* {@link android.media.AudioAttributes usage}
*
* @param usage the {@link android.media.AudioAttributes usage}
* @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
* {@code null} otherwise
* @throws IllegalArgumentException if the usage is invalid
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@Nullable
public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(
@AudioAttributes.AttributeUsage int usage) {
ensureFadingIsEnabled();
validateUsage(usage);
return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
/* isFadeIn= */ true);
}
/**
* Get the duration used to fade out players with {@link android.media.AudioAttributes}
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return duration in milliseconds if set for the audio attributes or
* {@link #DURATION_NOT_SET} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@IntRange(from = 0) @DurationMillisLong
public long getFadeOutDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
ensureFadingIsEnabled();
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
mAttrToFadeWrapperMap.get(audioAttributes), /* isFadeIn= */ false));
}
/**
* Get the duration used to fade-in players with {@link android.media.AudioAttributes}
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return duration in milliseconds if set for the audio attributes or
* {@link #DURATION_NOT_SET} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@IntRange(from = 0) @DurationMillisLong
public long getFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
ensureFadingIsEnabled();
return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
mAttrToFadeWrapperMap.get(audioAttributes), /* isFadeIn= */ true));
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
* {@link android.media.AudioAttributes}
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} if set for the audio attribute or
* {@code null} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@Nullable
public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(
@NonNull AudioAttributes audioAttributes) {
Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
ensureFadingIsEnabled();
return getVolumeShaperConfigFromWrapper(mAttrToFadeWrapperMap.get(audioAttributes),
/* isFadeIn= */ false);
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
* {@link android.media.AudioAttributes}
*
* @param audioAttributes {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} used for fading in if set for the
* audio attribute or {@code null} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
* @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
*/
@Nullable
public VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(
@NonNull AudioAttributes audioAttributes) {
Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
ensureFadingIsEnabled();
return getVolumeShaperConfigFromWrapper(mAttrToFadeWrapperMap.get(audioAttributes),
/* isFadeIn= */ true);
}
/**
* Get the list of {@link android.media.AudioAttributes} for whome the volume shaper
* configurations are defined
*
* @return list of {@link android.media.AudioAttributes} with valid volume shaper configs or
* empty list if none set.
*/
@NonNull
public List<AudioAttributes> getAudioAttributesWithVolumeShaperConfigs() {
return getAudioAttributesInternal();
}
/**
* Get the delay after which the offending players are faded back in
*
* Players are categorized as offending if they do not honor audio focus state changes. For
* example - when an app loses audio focus, it is expected that the app stops any active
* player in favor of the app(s) that gained audio focus. However, if the app do not stop the
* audio playback, such players are termed as offenders.
*
* @return delay in milliseconds
*/
@IntRange(from = 0) @DurationMillisLong
public long getFadeInDelayForOffenders() {
return mFadeInDelayForOffendersMillis;
}
/**
* Query if fade is enabled
*
* @return {@code true} if fading is enabled, {@code false} otherwise
*/
public boolean isFadeEnabled() {
return mFadeState != FADE_STATE_DISABLED;
}
/**
* Query if the usage is fadeable
*
* @param usage the {@link android.media.AudioAttributes usage}
* @return {@code true} if usage is fadeable, {@code false} when the fade state is set to
* {@link #FADE_STATE_DISABLED} or if the usage is not fadeable.
*/
public boolean isUsageFadeable(@AudioAttributes.AttributeUsage int usage) {
if (!isFadeEnabled()) {
return false;
}
return mFadeableUsages.contains(usage);
}
/**
* Query if the content type is unfadeable
*
* @param contentType the {@link android.media.AudioAttributes content type}
* @return {@code true} if content type is unfadeable or if fade state is set to
* {@link #FADE_STATE_DISABLED}, {@code false} otherwise
*/
public boolean isContentTypeUnfadeable(@AudioAttributes.AttributeContentType int contentType) {
if (!isFadeEnabled()) {
return true;
}
return mUnfadeableContentTypes.contains(contentType);
}
/**
* Query if the player type is unfadeable
*
* @param playerType the {@link android.media.AudioPlaybackConfiguration player type}
* @return {@code true} if player type is unfadeable or if fade state is set to
* {@link #FADE_STATE_DISABLED}, {@code false} otherwise
*/
public boolean isPlayerTypeUnfadeable(@AudioPlaybackConfiguration.PlayerType int playerType) {
if (!isFadeEnabled()) {
return true;
}
return mUnfadeablePlayerTypes.contains(playerType);
}
/**
* Query if the audio attributes is unfadeable
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @return {@code true} if audio attributes is unfadeable or if fade state is set to
* {@link #FADE_STATE_DISABLED}, {@code false} otherwise
* @throws NullPointerException if the audio attributes is {@code null}
*/
public boolean isAudioAttributesUnfadeable(@NonNull AudioAttributes audioAttributes) {
Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
if (!isFadeEnabled()) {
return true;
}
return mUnfadeableAudioAttributes.contains(audioAttributes);
}
/**
* Query if the uid is unfadeable
*
* @param uid the uid of application
* @return {@code true} if uid is unfadeable or if fade state is set to
* {@link #FADE_STATE_DISABLED}, {@code false} otherwise
*/
public boolean isUidUnfadeable(int uid) {
if (!isFadeEnabled()) {
return true;
}
return mUnfadeableUids.contains(uid);
}
/**
* Returns the default fade out duration (in milliseconds)
*/
public static @IntRange(from = 1) @DurationMillisLong long getDefaultFadeOutDurationMillis() {
return DEFAULT_FADE_OUT_DURATION_MS;
}
/**
* Returns the default fade in duration (in milliseconds)
*/
public static @IntRange(from = 1) @DurationMillisLong long getDefaultFadeInDurationMillis() {
return DEFAULT_FADE_IN_DURATION_MS;
}
@Override
public String toString() {
return "FadeManagerConfiguration { fade state = " + fadeStateToString(mFadeState)
+ ", fade out duration = " + mFadeOutDurationMillis
+ ", fade in duration = " + mFadeInDurationMillis
+ ", offenders fade in delay = " + mFadeInDelayForOffendersMillis
+ ", fade volume shapers for audio attributes = " + mAttrToFadeWrapperMap
+ ", fadeable usages = " + mFadeableUsages.toString()
+ ", unfadeable content types = " + mUnfadeableContentTypes.toString()
+ ", unfadeable player types = " + mUnfadeablePlayerTypes.toString()
+ ", unfadeable uids = " + mUnfadeableUids.toString()
+ ", unfadeable audio attributes = " + mUnfadeableAudioAttributes + "}";
}
/**
* Convert fade state into a human-readable string
*
* @param fadeState one of {@link #FADE_STATE_DISABLED} or {@link #FADE_STATE_ENABLED_DEFAULT}
* @return human-readable string
* @hide
*/
@NonNull
public static String fadeStateToString(@FadeStateEnum int fadeState) {
switch (fadeState) {
case FADE_STATE_DISABLED:
return "FADE_STATE_DISABLED";
case FADE_STATE_ENABLED_DEFAULT:
return "FADE_STATE_ENABLED_DEFAULT";
default:
return "unknown fade state: " + fadeState;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FadeManagerConfiguration)) {
return false;
}
FadeManagerConfiguration rhs = (FadeManagerConfiguration) o;
return mUsageToFadeWrapperMap.contentEquals(rhs.mUsageToFadeWrapperMap)
&& mAttrToFadeWrapperMap.equals(rhs.mAttrToFadeWrapperMap)
&& Arrays.equals(mFadeableUsages.toArray(), rhs.mFadeableUsages.toArray())
&& Arrays.equals(mUnfadeableContentTypes.toArray(),
rhs.mUnfadeableContentTypes.toArray())
&& Arrays.equals(mUnfadeablePlayerTypes.toArray(),
rhs.mUnfadeablePlayerTypes.toArray())
&& Arrays.equals(mUnfadeableUids.toArray(), rhs.mUnfadeableUids.toArray())
&& mUnfadeableAudioAttributes.equals(rhs.mUnfadeableAudioAttributes)
&& mFadeState == rhs.mFadeState
&& mFadeOutDurationMillis == rhs.mFadeOutDurationMillis
&& mFadeInDurationMillis == rhs.mFadeInDurationMillis
&& mFadeInDelayForOffendersMillis == rhs.mFadeInDelayForOffendersMillis;
}
@Override
public int hashCode() {
return Objects.hash(mUsageToFadeWrapperMap, mAttrToFadeWrapperMap, mFadeableUsages,
mUnfadeableContentTypes, mUnfadeablePlayerTypes, mUnfadeableAudioAttributes,
mUnfadeableUids, mFadeState, mFadeOutDurationMillis, mFadeInDurationMillis,
mFadeInDelayForOffendersMillis);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mFadeState);
dest.writeLong(mFadeOutDurationMillis);
dest.writeLong(mFadeInDurationMillis);
dest.writeLong(mFadeInDelayForOffendersMillis);
dest.writeTypedSparseArray(mUsageToFadeWrapperMap, flags);
dest.writeMap(mAttrToFadeWrapperMap);
dest.writeIntArray(mFadeableUsages.toArray());
dest.writeIntArray(mUnfadeableContentTypes.toArray());
dest.writeIntArray(mUnfadeablePlayerTypes.toArray());
dest.writeIntArray(mUnfadeableUids.toArray());
dest.writeTypedList(mUnfadeableAudioAttributes, flags);
}
/**
* Creates fade manage configuration from parcel
*
* @hide
*/
@VisibleForTesting()
FadeManagerConfiguration(Parcel in) {
int fadeState = in.readInt();
long fadeOutDurationMillis = in.readLong();
long fadeInDurationMillis = in.readLong();
long fadeInDelayForOffenders = in.readLong();
SparseArray<FadeVolumeShaperConfigsWrapper> usageToWrapperMap =
in.createTypedSparseArray(FadeVolumeShaperConfigsWrapper.CREATOR);
ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap =
new ArrayMap<>();
in.readMap(attrToFadeWrapperMap, getClass().getClassLoader(), AudioAttributes.class,
FadeVolumeShaperConfigsWrapper.class);
int[] fadeableUsages = in.createIntArray();
int[] unfadeableContentTypes = in.createIntArray();
int[] unfadeablePlayerTypes = in.createIntArray();
int[] unfadeableUids = in.createIntArray();
List<AudioAttributes> unfadeableAudioAttributes = new ArrayList<>();
in.readTypedList(unfadeableAudioAttributes, AudioAttributes.CREATOR);
this.mFadeState = fadeState;
this.mFadeOutDurationMillis = fadeOutDurationMillis;
this.mFadeInDurationMillis = fadeInDurationMillis;
this.mFadeInDelayForOffendersMillis = fadeInDelayForOffenders;
this.mUsageToFadeWrapperMap = usageToWrapperMap;
this.mAttrToFadeWrapperMap = attrToFadeWrapperMap;
this.mFadeableUsages = IntArray.wrap(fadeableUsages);
this.mUnfadeableContentTypes = IntArray.wrap(unfadeableContentTypes);
this.mUnfadeablePlayerTypes = IntArray.wrap(unfadeablePlayerTypes);
this.mUnfadeableUids = IntArray.wrap(unfadeableUids);
this.mUnfadeableAudioAttributes = unfadeableAudioAttributes;
}
@NonNull
public static final Creator<FadeManagerConfiguration> CREATOR = new Creator<>() {
@Override
@NonNull
public FadeManagerConfiguration createFromParcel(@NonNull Parcel in) {
return new FadeManagerConfiguration(in);
}
@Override
@NonNull
public FadeManagerConfiguration[] newArray(int size) {
return new FadeManagerConfiguration[size];
}
};
private long getDurationForVolumeShaperConfig(VolumeShaper.Configuration config) {
return config != null ? config.getDuration() : DURATION_NOT_SET;
}
private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
// if no volume shaper config is available, return null
if (wrapper == null) {
return null;
}
if (isFadeIn) {
return wrapper.getFadeInVolShaperConfig();
}
return wrapper.getFadeOutVolShaperConfig();
}
private List<AudioAttributes> getAudioAttributesInternal() {
List<AudioAttributes> attrs = new ArrayList<>(mAttrToFadeWrapperMap.size());
for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
attrs.add(mAttrToFadeWrapperMap.keyAt(index));
}
return attrs;
}
private static boolean isUsageValid(int usage) {
return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage)
|| AudioAttributes.isHiddenUsage(usage);
}
private void ensureFadingIsEnabled() {
if (!isFadeEnabled()) {
throw new IllegalStateException("Method call not allowed when fade is disabled");
}
}
private static void validateUsage(int usage) {
Preconditions.checkArgument(isUsageValid(usage), "Invalid usage: %s", usage);
}
private static IntArray convertIntegerListToIntArray(List<Integer> integerList) {
if (integerList == null) {
return new IntArray();
}
IntArray intArray = new IntArray(integerList.size());
for (int index = 0; index < integerList.size(); index++) {
intArray.add(integerList.get(index));
}
return intArray;
}
private static List<Integer> convertIntArrayToIntegerList(IntArray intArray) {
if (intArray == null) {
return new ArrayList<>();
}
ArrayList<Integer> integerArrayList = new ArrayList<>(intArray.size());
for (int index = 0; index < intArray.size(); index++) {
integerArrayList.add(intArray.get(index));
}
return integerArrayList;
}
/**
* Builder class for {@link FadeManagerConfiguration} objects.
*
* <p><b>Notes:</b>
* <ul>
* <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT}, the builder expects at
* least one valid usage to be set/added. Failure to do so will result in an exception
* during {@link #build()}</li>
* <li>Every usage added to the fadeable list should have corresponding volume shaper
* configs defined. This can be achieved by setting either the duration or volume shaper
* config through {@link #setFadeOutDurationForUsage(int, long)} or
* {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
* <li> It is recommended to set volume shaper configurations individually for fade out and
* fade in</li>
* <li>For any incomplete volume shaper configurations, a volume shaper configuration will
* be created using either the default fade durations or the ones provided as part of the
* {@link #Builder(long, long)}</li>
* <li>Additional volume shaper configs can also configured for a given usage
* with additional attributes like content-type in order to achieve finer fade controls.
* See:
* {@link #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
* VolumeShaper.Configuration)} and
* {@link #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
* VolumeShaper.Configuration)} </li>
* </ul>
*
*/
@SuppressWarnings("WeakerAccess")
public static final class Builder {
private static final int INVALID_INDEX = -1;
private static final long IS_BUILDER_USED_FIELD_SET = 1 << 0;
private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1;
private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2;
/**
* delay after which a faded out player will be faded back in. This will be heard by the
* user only in the case of unmuting players that didn't respect audio focus and didn't
* stop/pause when their app lost focus.
* This is the amount of time between the app being notified of the focus loss
* (when its muted by the fade out), and the time fade in (to unmute) starts
*/
private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2_000;
private static final IntArray DEFAULT_UNFADEABLE_PLAYER_TYPES = IntArray.wrap(new int[]{
AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
});
private static final IntArray DEFAULT_UNFADEABLE_CONTENT_TYPES = IntArray.wrap(new int[]{
AudioAttributes.CONTENT_TYPE_SPEECH
});
private static final IntArray DEFAULT_FADEABLE_USAGES = IntArray.wrap(new int[]{
AudioAttributes.USAGE_GAME,
AudioAttributes.USAGE_MEDIA
});
private int mFadeState = FADE_STATE_ENABLED_DEFAULT;
private @DurationMillisLong long mFadeInDelayForOffendersMillis =
DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
private @DurationMillisLong long mFadeOutDurationMillis;
private @DurationMillisLong long mFadeInDurationMillis;
private long mBuilderFieldsSet;
private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap =
new SparseArray<>();
private ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap =
new ArrayMap<>();
private IntArray mFadeableUsages = new IntArray();
private IntArray mUnfadeableContentTypes = new IntArray();
// Player types are not yet configurable
private IntArray mUnfadeablePlayerTypes = DEFAULT_UNFADEABLE_PLAYER_TYPES;
private IntArray mUnfadeableUids = new IntArray();
private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>();
/**
* Constructs a new Builder with {@link #DEFAULT_FADE_OUT_DURATION_MS} and
* {@link #DEFAULT_FADE_IN_DURATION_MS} durations.
*/
public Builder() {
mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS;
mFadeInDurationMillis = DEFAULT_FADE_IN_DURATION_MS;
}
/**
* Constructs a new Builder with the provided fade out and fade in durations
*
* @param fadeOutDurationMillis duration in milliseconds used for fading out
* @param fadeInDurationMills duration in milliseconds used for fading in
*/
public Builder(@IntRange(from = 1) @DurationMillisLong long fadeOutDurationMillis,
@IntRange(from = 1) @DurationMillisLong long fadeInDurationMills) {
mFadeOutDurationMillis = fadeOutDurationMillis;
mFadeInDurationMillis = fadeInDurationMills;
}
/**
* Constructs a new Builder from the given {@link FadeManagerConfiguration}
*
* @param fmc the {@link FadeManagerConfiguration} object whose data will be reused in the
* new builder
*/
public Builder(@NonNull FadeManagerConfiguration fmc) {
mFadeState = fmc.mFadeState;
copyUsageToFadeWrapperMapInternal(fmc.mUsageToFadeWrapperMap);
mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>(
fmc.mAttrToFadeWrapperMap);
mFadeableUsages = fmc.mFadeableUsages.clone();
setFlag(IS_FADEABLE_USAGES_FIELD_SET);
mUnfadeableContentTypes = fmc.mUnfadeableContentTypes.clone();
setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
mUnfadeablePlayerTypes = fmc.mUnfadeablePlayerTypes.clone();
mUnfadeableUids = fmc.mUnfadeableUids.clone();
mUnfadeableAudioAttributes = new ArrayList<>(fmc.mUnfadeableAudioAttributes);
mFadeOutDurationMillis = fmc.mFadeOutDurationMillis;
mFadeInDurationMillis = fmc.mFadeInDurationMillis;
}
/**
* Set the overall fade state
*
* @param state one of the {@link #FADE_STATE_DISABLED} or
* {@link #FADE_STATE_ENABLED_DEFAULT} states
* @return the same Builder instance
* @throws IllegalArgumentException if the fade state is invalid
* @see #getFadeState()
*/
@NonNull
public Builder setFadeState(@FadeStateEnum int state) {
validateFadeState(state);
mFadeState = state;
return this;
}
/**
* Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
* {@link android.media.AudioAttributes usage}
* <p>
* This method accepts {@code null} for volume shaper config to clear a previously set
* configuration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)})
*
* @param usage the {@link android.media.AudioAttributes usage} of target player
* @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
* to fade out players with usage
* @return the same Builder instance
* @throws IllegalArgumentException if the usage is invalid
* @see #getFadeOutVolumeShaperConfigForUsage(int)
*/
@NonNull
public Builder setFadeOutVolumeShaperConfigForUsage(
@AudioAttributes.AttributeUsage int usage,
@Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
validateUsage(usage);
getFadeVolShaperConfigWrapperForUsage(usage)
.setFadeOutVolShaperConfig(fadeOutVShaperConfig);
cleanupInactiveWrapperEntries(usage);
return this;
}
/**
* Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
* {@link android.media.AudioAttributes usage}
* <p>
* This method accepts {@code null} for volume shaper config to clear a previously set
* configuration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)})
*
* @param usage the {@link android.media.AudioAttributes usage}
* @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
* to fade in players with usage
* @return the same Builder instance
* @throws IllegalArgumentException if the usage is invalid
* @see #getFadeInVolumeShaperConfigForUsage(int)
*/
@NonNull
public Builder setFadeInVolumeShaperConfigForUsage(
@AudioAttributes.AttributeUsage int usage,
@Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
validateUsage(usage);
getFadeVolShaperConfigWrapperForUsage(usage)
.setFadeInVolShaperConfig(fadeInVShaperConfig);
cleanupInactiveWrapperEntries(usage);
return this;
}
/**
* Set the duration used for fading out players with
* {@link android.media.AudioAttributes usage}
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade out players with given usage.
* <p>
* In order to clear previously set duration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
* {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
* {@code null}
*
* @param usage the {@link android.media.AudioAttributes usage} of target player
* @param fadeOutDurationMillis positive duration in milliseconds or
* {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade out duration is non-positive with the
* exception of {@link #DURATION_NOT_SET}
* @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
* @see #getFadeOutDurationForUsage(int)
*/
@NonNull
public Builder setFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage,
@IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis) {
validateUsage(usage);
VolumeShaper.Configuration fadeOutVShaperConfig =
createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
setFadeOutVolumeShaperConfigForUsage(usage, fadeOutVShaperConfig);
return this;
}
/**
* Set the duration used for fading in players with
* {@link android.media.AudioAttributes usage}
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade in players with given usage.
* <p>
* <b>Note: </b>In order to clear previously set duration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
* {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
* {@code null}
*
* @param usage the {@link android.media.AudioAttributes usage} of target player
* @param fadeInDurationMillis positive duration in milliseconds or
* {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade in duration is non-positive with the
* exception of {@link #DURATION_NOT_SET}
* @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
* @see #getFadeInDurationForUsage(int)
*/
@NonNull
public Builder setFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage,
@IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis) {
validateUsage(usage);
VolumeShaper.Configuration fadeInVShaperConfig =
createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
setFadeInVolumeShaperConfigForUsage(usage, fadeInVShaperConfig);
return this;
}
/**
* Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
* {@link android.media.AudioAttributes}
* <p>
* This method accepts {@code null} for volume shaper config to clear a previously set
* configuration (example, set through
* {@link #Builder(android.media.FadeManagerConfiguration)})
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
* fade out players with audio attribute
* @return the same Builder instance
* @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes)
*/
@NonNull
public Builder setFadeOutVolumeShaperConfigForAudioAttributes(
@NonNull AudioAttributes audioAttributes,
@Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
getFadeVolShaperConfigWrapperForAttr(audioAttributes)
.setFadeOutVolShaperConfig(fadeOutVShaperConfig);
cleanupInactiveWrapperEntries(audioAttributes);
return this;
}
/**
* Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
* {@link android.media.AudioAttributes}
*
* <p>This method accepts {@code null} for volume shaper config to clear a previously set
* configuration (example, set through
* {@link #Builder(android.media.FadeManagerConfiguration)})
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
* fade in players with audio attribute
* @return the same Builder instance
* @throws NullPointerException if the audio attributes is {@code null}
* @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes)
*/
@NonNull
public Builder setFadeInVolumeShaperConfigForAudioAttributes(
@NonNull AudioAttributes audioAttributes,
@Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
getFadeVolShaperConfigWrapperForAttr(audioAttributes)
.setFadeInVolShaperConfig(fadeInVShaperConfig);
cleanupInactiveWrapperEntries(audioAttributes);
return this;
}
/**
* Set the duration used for fading out players of type
* {@link android.media.AudioAttributes}.
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade out players with given usage.
* <p>
* <b>Note: </b>In order to clear previously set duration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
* {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
* {@code null}
*
* @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out
* duration will be set/updated/reset
* @param fadeOutDurationMillis positive duration in milliseconds or
* {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade out duration is non-positive with the
* exception of {@link #DURATION_NOT_SET}
* @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
* @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
* VolumeShaper.Configuration)
*/
@NonNull
public Builder setFadeOutDurationForAudioAttributes(
@NonNull AudioAttributes audioAttributes,
@IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis) {
Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
VolumeShaper.Configuration fadeOutVShaperConfig =
createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
setFadeOutVolumeShaperConfigForAudioAttributes(audioAttributes, fadeOutVShaperConfig);
return this;
}
/**
* Set the duration used for fading in players of type {@link android.media.AudioAttributes}
* <p>
* A Volume shaper configuration is generated with the provided duration and default
* volume curve definitions. This config is then used to fade in players with given usage.
* <p>
* <b>Note: </b>In order to clear previously set duration (example, if set through
* {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
* {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
* {@code null}
*
* @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in
* duration will be set/updated/reset
* @param fadeInDurationMillis positive duration in milliseconds or
* {@link #DURATION_NOT_SET}
* @return the same Builder instance
* @throws IllegalArgumentException if the fade in duration is non-positive with the
* exception of {@link #DURATION_NOT_SET}
* @see #getFadeInDurationForAudioAttributes(AudioAttributes)
* @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
* VolumeShaper.Configuration)
*/
@NonNull
public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes,
@IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis) {
Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
VolumeShaper.Configuration fadeInVShaperConfig =
createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
setFadeInVolumeShaperConfigForAudioAttributes(audioAttributes, fadeInVShaperConfig);
return this;
}
/**
* Set the list of {@link android.media.AudioAttributes usage} that can be faded
*
* <p>This is a positive list. Players with matching usage will be considered for fading.
* Usages that are not part of this list will not be faded
*
* <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one
* usage to be set/added. Failure to do so will result in an exception during
* {@link #build()}
*
* @param usages List of the {@link android.media.AudioAttributes usages}
* @return the same Builder instance
* @throws IllegalArgumentException if the usages are invalid
* @see #getFadeableUsages()
*/
@NonNull
public Builder setFadeableUsages(@NonNull List<Integer> usages) {
Objects.requireNonNull(usages, "List of usages cannot be null");
validateUsages(usages);
setFlag(IS_FADEABLE_USAGES_FIELD_SET);
mFadeableUsages.clear();
mFadeableUsages.addAll(convertIntegerListToIntArray(usages));
return this;
}
/**
* Add the {@link android.media.AudioAttributes usage} to the fadeable list
*
* @param usage the {@link android.media.AudioAttributes usage}
* @return the same Builder instance
* @throws IllegalArgumentException if the usage is invalid
* @see #getFadeableUsages()
* @see #setFadeableUsages(List)
*/
@NonNull
public Builder addFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
validateUsage(usage);
setFlag(IS_FADEABLE_USAGES_FIELD_SET);
if (!mFadeableUsages.contains(usage)) {
mFadeableUsages.add(usage);
}
return this;
}
/**
* Clears the fadeable {@link android.media.AudioAttributes usage} list
*
* <p>This can be used to reset the list when using a copy constructor
*
* @return the same Builder instance
* @see #getFadeableUsages()
* @see #setFadeableUsages(List)
*/
@NonNull
public Builder clearFadeableUsages() {
setFlag(IS_FADEABLE_USAGES_FIELD_SET);
mFadeableUsages.clear();
return this;
}
/**
* Set the list of {@link android.media.AudioAttributes content type} that can not be faded
*
* <p>This is a negative list. Players with matching content type of this list will not be
* faded. Content types that are not part of this list will be considered for fading.
*
* <p>Passing an empty list as input clears the existing list. This can be used to
* reset the list when using a copy constructor
*
* @param contentTypes list of {@link android.media.AudioAttributes content types}
* @return the same Builder instance
* @throws IllegalArgumentException if the content types are invalid
* @see #getUnfadeableContentTypes()
*/
@NonNull
public Builder setUnfadeableContentTypes(@NonNull List<Integer> contentTypes) {
Objects.requireNonNull(contentTypes, "List of content types cannot be null");
validateContentTypes(contentTypes);
setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
mUnfadeableContentTypes.clear();
mUnfadeableContentTypes.addAll(convertIntegerListToIntArray(contentTypes));
return this;
}
/**
* Add the {@link android.media.AudioAttributes content type} to unfadeable list
*
* @param contentType the {@link android.media.AudioAttributes content type}
* @return the same Builder instance
* @throws IllegalArgumentException if the content type is invalid
* @see #setUnfadeableContentTypes(List)
* @see #getUnfadeableContentTypes()
*/
@NonNull
public Builder addUnfadeableContentType(
@AudioAttributes.AttributeContentType int contentType) {
validateContentType(contentType);
setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
if (!mUnfadeableContentTypes.contains(contentType)) {
mUnfadeableContentTypes.add(contentType);
}
return this;
}
/**
* Clears the unfadeable {@link android.media.AudioAttributes content type} list
*
* <p>This can be used to reset the list when using a copy constructor
*
* @return the same Builder instance
* @see #setUnfadeableContentTypes(List)
* @see #getUnfadeableContentTypes()
*/
@NonNull
public Builder clearUnfadeableContentTypes() {
setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
mUnfadeableContentTypes.clear();
return this;
}
/**
* Set the uids that cannot be faded
*
* <p>This is a negative list. Players with matching uid of this list will not be faded.
* Uids that are not part of this list shall be considered for fading.
*
* @param uids list of uids
* @return the same Builder instance
* @see #getUnfadeableUids()
*/
@NonNull
public Builder setUnfadeableUids(@NonNull List<Integer> uids) {
Objects.requireNonNull(uids, "List of uids cannot be null");
mUnfadeableUids.clear();
mUnfadeableUids.addAll(convertIntegerListToIntArray(uids));
return this;
}
/**
* Add uid to unfadeable list
*
* @param uid client uid
* @return the same Builder instance
* @see #setUnfadeableUids(List)
* @see #getUnfadeableUids()
*/
@NonNull
public Builder addUnfadeableUid(int uid) {
if (!mUnfadeableUids.contains(uid)) {
mUnfadeableUids.add(uid);
}
return this;
}
/**
* Clears the unfadeable uid list
*
* <p>This can be used to reset the list when using a copy constructor.
*
* @return the same Builder instance
* @see #setUnfadeableUids(List)
* @see #getUnfadeableUids()
*/
@NonNull
public Builder clearUnfadeableUids() {
mUnfadeableUids.clear();
return this;
}
/**
* Set the list of {@link android.media.AudioAttributes} that can not be faded
*
* <p>This is a negative list. Players with matching audio attributes of this list will not
* be faded. Audio attributes that are not part of this list shall be considered for fading.
*
* <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can
* negatively impact fadeability decision (if such an audio attribute and corresponding
* usage fall into opposing lists).
* For example:
* <pre class=prettyprint>
* AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre>
* is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}.
* It is an undefined behavior to have an {@link android.media.AudioAttributes usage} in the
* fadeable usage list and the corresponding generic {@link android.media.AudioAttributes}
* in the unfadeable list. Such cases will result in an exception during {@link #build()}.
*
* @param attrs list of {@link android.media.AudioAttributes}
* @return the same Builder instance
* @see #getUnfadeableAudioAttributes()
*/
@NonNull
public Builder setUnfadeableAudioAttributes(@NonNull List<AudioAttributes> attrs) {
Objects.requireNonNull(attrs, "List of audio attributes cannot be null");
mUnfadeableAudioAttributes.clear();
mUnfadeableAudioAttributes.addAll(attrs);
return this;
}
/**
* Add the {@link android.media.AudioAttributes} to the unfadeable list
*
* @param audioAttributes the {@link android.media.AudioAttributes}
* @return the same Builder instance
* @see #setUnfadeableAudioAttributes(List)
* @see #getUnfadeableAudioAttributes()
*/
@NonNull
public Builder addUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
if (!mUnfadeableAudioAttributes.contains(audioAttributes)) {
mUnfadeableAudioAttributes.add(audioAttributes);
}
return this;
}
/**
* Clears the unfadeable {@link android.media.AudioAttributes} list.
*
* <p>This can be used to reset the list when using a copy constructor.
*
* @return the same Builder instance
* @see #getUnfadeableAudioAttributes()
*/
@NonNull
public Builder clearUnfadeableAudioAttributes() {
mUnfadeableAudioAttributes.clear();
return this;
}
/**
* Set the delay after which the offending faded out player will be faded in.
*
* <p>This is the amount of time between the app being notified of the focus loss (when its
* muted by the fade out), and the time fade in (to unmute) starts
*
* @param delayMillis delay in milliseconds
* @return the same Builder instance
* @throws IllegalArgumentException if the delay is negative
* @see #getFadeInDelayForOffenders()
*/
@NonNull
public Builder setFadeInDelayForOffenders(
@IntRange(from = 0) @DurationMillisLong long delayMillis) {
Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative");
mFadeInDelayForOffendersMillis = delayMillis;
return this;
}
/**
* Builds the {@link FadeManagerConfiguration} with all of the fade configurations that
* have been set.
*
* @return a new {@link FadeManagerConfiguration} object
*/
@NonNull
public FadeManagerConfiguration build() {
if (!checkNotSet(IS_BUILDER_USED_FIELD_SET)) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
setFlag(IS_BUILDER_USED_FIELD_SET);
if (checkNotSet(IS_FADEABLE_USAGES_FIELD_SET)) {
mFadeableUsages = DEFAULT_FADEABLE_USAGES;
setVolShaperConfigsForUsages(mFadeableUsages);
}
if (checkNotSet(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET)) {
mUnfadeableContentTypes = DEFAULT_UNFADEABLE_CONTENT_TYPES;
}
validateFadeConfigurations();
return new FadeManagerConfiguration(mFadeState, mFadeOutDurationMillis,
mFadeInDurationMillis, mFadeInDelayForOffendersMillis, mUsageToFadeWrapperMap,
mAttrToFadeWrapperMap, mFadeableUsages, mUnfadeableContentTypes,
mUnfadeablePlayerTypes, mUnfadeableUids, mUnfadeableAudioAttributes);
}
private void setFlag(long flag) {
mBuilderFieldsSet |= flag;
}
private boolean checkNotSet(long flag) {
return (mBuilderFieldsSet & flag) == 0;
}
private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForUsage(int usage) {
if (!mUsageToFadeWrapperMap.contains(usage)) {
mUsageToFadeWrapperMap.put(usage, new FadeVolumeShaperConfigsWrapper());
}
return mUsageToFadeWrapperMap.get(usage);
}
private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForAttr(
AudioAttributes attr) {
// if no entry, create a new one for setting/clearing
if (!mAttrToFadeWrapperMap.containsKey(attr)) {
mAttrToFadeWrapperMap.put(attr, new FadeVolumeShaperConfigsWrapper());
}
return mAttrToFadeWrapperMap.get(attr);
}
private VolumeShaper.Configuration createVolShaperConfigForDuration(long duration,
boolean isFadeIn) {
// used to reset the volume shaper config setting
if (duration == DURATION_NOT_SET) {
return null;
}
VolumeShaper.Configuration.Builder builder = new VolumeShaper.Configuration.Builder()
.setId(VOLUME_SHAPER_SYSTEM_FADE_ID)
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
.setDuration(duration);
if (isFadeIn) {
builder.setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
/* volumes= */ new float[]{0.f, 0.30f, 1.0f});
} else {
builder.setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
/* volumes= */ new float[]{1.f, 0.65f, 0.0f});
}
return builder.build();
}
private void cleanupInactiveWrapperEntries(int usage) {
FadeVolumeShaperConfigsWrapper fmcw = mUsageToFadeWrapperMap.get(usage);
// cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
if (fmcw != null && fmcw.isInactive()) {
mUsageToFadeWrapperMap.remove(usage);
}
}
private void cleanupInactiveWrapperEntries(AudioAttributes attr) {
FadeVolumeShaperConfigsWrapper fmcw = mAttrToFadeWrapperMap.get(attr);
// cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
if (fmcw != null && fmcw.isInactive()) {
mAttrToFadeWrapperMap.remove(attr);
}
}
private void setVolShaperConfigsForUsages(IntArray usages) {
// set default volume shaper configs for fadeable usages
for (int index = 0; index < usages.size(); index++) {
setMissingVolShaperConfigsForWrapper(
getFadeVolShaperConfigWrapperForUsage(usages.get(index)));
}
}
private void setMissingVolShaperConfigsForWrapper(FadeVolumeShaperConfigsWrapper wrapper) {
if (!wrapper.isFadeOutConfigActive()) {
wrapper.setFadeOutVolShaperConfig(createVolShaperConfigForDuration(
mFadeOutDurationMillis, /* isFadeIn= */ false));
}
if (!wrapper.isFadeInConfigActive()) {
wrapper.setFadeInVolShaperConfig(createVolShaperConfigForDuration(
mFadeInDurationMillis, /* isFadeIn= */ true));
}
}
private void copyUsageToFadeWrapperMapInternal(
SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap) {
for (int index = 0; index < usageToFadeWrapperMap.size(); index++) {
mUsageToFadeWrapperMap.put(usageToFadeWrapperMap.keyAt(index),
new FadeVolumeShaperConfigsWrapper(usageToFadeWrapperMap.valueAt(index)));
}
}
private void validateFadeState(int state) {
switch(state) {
case FADE_STATE_DISABLED:
case FADE_STATE_ENABLED_DEFAULT:
break;
default:
throw new IllegalArgumentException("Unknown fade state: " + state);
}
}
private void validateUsages(List<Integer> usages) {
for (int index = 0; index < usages.size(); index++) {
validateUsage(usages.get(index));
}
}
private void validateContentTypes(List<Integer> contentTypes) {
for (int index = 0; index < contentTypes.size(); index++) {
validateContentType(contentTypes.get(index));
}
}
private void validateContentType(int contentType) {
Preconditions.checkArgument(AudioAttributes.isSdkContentType(contentType),
"Invalid content type: ", contentType);
}
private void validateFadeConfigurations() {
validateFadeableUsages();
validateFadeVolumeShaperConfigsWrappers();
validateUnfadeableAudioAttributes();
}
/** Ensure fadeable usage list meets config requirements */
private void validateFadeableUsages() {
// ensure at least one fadeable usage
Preconditions.checkArgumentPositive(mFadeableUsages.size(),
"Fadeable usage list cannot be empty when state set to enabled");
// ensure all fadeable usages have volume shaper configs - both fade in and out
for (int index = 0; index < mFadeableUsages.size(); index++) {
setMissingVolShaperConfigsForWrapper(
getFadeVolShaperConfigWrapperForUsage(mFadeableUsages.get(index)));
}
}
/** Ensure Fade volume shaper config wrappers meet requirements */
private void validateFadeVolumeShaperConfigsWrappers() {
// ensure both fade in & out volume shaper configs are defined for all wrappers
// for usages -
for (int index = 0; index < mUsageToFadeWrapperMap.size(); index++) {
setMissingVolShaperConfigsForWrapper(
getFadeVolShaperConfigWrapperForUsage(mUsageToFadeWrapperMap.keyAt(index)));
}
// for additional audio attributes -
for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
setMissingVolShaperConfigsForWrapper(
getFadeVolShaperConfigWrapperForAttr(mAttrToFadeWrapperMap.keyAt(index)));
}
}
/** Ensure Unfadeable attributes meet configuration requirements */
private void validateUnfadeableAudioAttributes() {
// ensure no generic AudioAttributes in unfadeable list with matching usage in fadeable
// list. failure results in an undefined behavior as the audio attributes
// shall be both fadeable (because of the usage) and unfadeable at the same time.
for (int index = 0; index < mUnfadeableAudioAttributes.size(); index++) {
AudioAttributes targetAttr = mUnfadeableAudioAttributes.get(index);
int usage = targetAttr.getSystemUsage();
boolean isFadeableUsage = mFadeableUsages.contains(usage);
// cannot have a generic audio attribute that also is a fadeable usage
Preconditions.checkArgument(
!isFadeableUsage || (isFadeableUsage && !isGeneric(targetAttr)),
"Unfadeable audio attributes cannot be generic of the fadeable usage");
}
}
private static boolean isGeneric(AudioAttributes attr) {
return (attr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN
&& attr.getFlags() == 0x0
&& attr.getBundle() == null
&& attr.getTags().isEmpty());
}
}
private static final class FadeVolumeShaperConfigsWrapper implements Parcelable {
// null volume shaper config refers to either init state or if its cleared/reset
private @Nullable VolumeShaper.Configuration mFadeOutVolShaperConfig;
private @Nullable VolumeShaper.Configuration mFadeInVolShaperConfig;
FadeVolumeShaperConfigsWrapper() {}
FadeVolumeShaperConfigsWrapper(@NonNull FadeVolumeShaperConfigsWrapper wrapper) {
Objects.requireNonNull(wrapper, "Fade volume shaper configs wrapper cannot be null");
this.mFadeOutVolShaperConfig = wrapper.mFadeOutVolShaperConfig;
this.mFadeInVolShaperConfig = wrapper.mFadeInVolShaperConfig;
}
public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) {
mFadeOutVolShaperConfig = fadeOutConfig;
}
public void setFadeInVolShaperConfig(@Nullable VolumeShaper.Configuration fadeInConfig) {
mFadeInVolShaperConfig = fadeInConfig;
}
/**
* Query fade out volume shaper config
*
* @return configured fade out volume shaper config or {@code null} when initialized/reset
*/
@Nullable
public VolumeShaper.Configuration getFadeOutVolShaperConfig() {
return mFadeOutVolShaperConfig;
}
/**
* Query fade in volume shaper config
*
* @return configured fade in volume shaper config or {@code null} when initialized/reset
*/
@Nullable
public VolumeShaper.Configuration getFadeInVolShaperConfig() {
return mFadeInVolShaperConfig;
}
/**
* Wrapper is inactive if both fade out and in configs are cleared.
*
* @return {@code true} if configs are cleared. {@code false} if either of the configs is
* set
*/
public boolean isInactive() {
return !isFadeOutConfigActive() && !isFadeInConfigActive();
}
boolean isFadeOutConfigActive() {
return mFadeOutVolShaperConfig != null;
}
boolean isFadeInConfigActive() {
return mFadeInVolShaperConfig != null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof FadeVolumeShaperConfigsWrapper)) {
return false;
}
FadeVolumeShaperConfigsWrapper rhs = (FadeVolumeShaperConfigsWrapper) o;
if (mFadeInVolShaperConfig == null && rhs.mFadeInVolShaperConfig == null
&& mFadeOutVolShaperConfig == null && rhs.mFadeOutVolShaperConfig == null) {
return true;
}
boolean isEqual;
if (mFadeOutVolShaperConfig != null) {
isEqual = mFadeOutVolShaperConfig.equals(rhs.mFadeOutVolShaperConfig);
} else if (rhs.mFadeOutVolShaperConfig != null) {
return false;
} else {
isEqual = true;
}
if (mFadeInVolShaperConfig != null) {
isEqual = isEqual && mFadeInVolShaperConfig.equals(rhs.mFadeInVolShaperConfig);
} else if (rhs.mFadeInVolShaperConfig != null) {
return false;
}
return isEqual;
}
@Override
public int hashCode() {
return Objects.hash(mFadeOutVolShaperConfig, mFadeInVolShaperConfig);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
mFadeOutVolShaperConfig.writeToParcel(dest, flags);
mFadeInVolShaperConfig.writeToParcel(dest, flags);
}
/**
* Creates fade volume shaper config wrapper from parcel
*
* @hide
*/
@VisibleForTesting()
FadeVolumeShaperConfigsWrapper(Parcel in) {
mFadeOutVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
mFadeInVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
}
@NonNull
public static final Creator<FadeVolumeShaperConfigsWrapper> CREATOR = new Creator<>() {
@Override
@NonNull
public FadeVolumeShaperConfigsWrapper createFromParcel(@NonNull Parcel in) {
return new FadeVolumeShaperConfigsWrapper(in);
}
@Override
@NonNull
public FadeVolumeShaperConfigsWrapper[] newArray(int size) {
return new FadeVolumeShaperConfigsWrapper[size];
}
};
}
}