1693 lines
73 KiB
Java
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];
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|