Support configurable fade properties
Automotive audio systems support powerfull amplifiers
that have specific requirement of fade properties.
Automotive partners also require dynamic fade
properties in order to meet geography based legal
requirements.
This feature supports configuring the following
fade properties:
- Fadeable usages
- Unfadeable content types
- Unfadeable UIDs
- Unfadeable audio attributes
- Volume shaper config per usage or audio attributes
- fade duration per usage or audio attributes
Bug: 186905459
Bug: 307354764
API-Coverage-Bug: 308666800
Test: atest -c FadeManagerConfigurationUnitTest
Change-Id: I0e3d76a85c53e597a0937886b5b4d615f725bd3b
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 941d842..0cc4207 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -425,7 +425,10 @@
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
package: "com.android.media.flags",
- srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
+ srcs: [
+ "media/java/android/media/flags/media_better_together.aconfig",
+ "media/java/android/media/flags/fade_manager_configuration.aconfig",
+ ],
}
java_aconfig_library {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bf9419fe..4be282b 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -29,6 +29,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
@@ -330,7 +331,7 @@
* @hide
* Array of all usage types exposed in the SDK that applications can use.
*/
- public final static int[] SDK_USAGES = {
+ public static final IntArray SDK_USAGES = IntArray.wrap(new int[] {
USAGE_UNKNOWN,
USAGE_MEDIA,
USAGE_VOICE_COMMUNICATION,
@@ -347,14 +348,14 @@
USAGE_ASSISTANCE_SONIFICATION,
USAGE_GAME,
USAGE_ASSISTANT,
- };
+ });
/**
* @hide
*/
@TestApi
public static int[] getSdkUsages() {
- return SDK_USAGES;
+ return SDK_USAGES.toArray();
}
/**
@@ -567,6 +568,15 @@
private String mFormattedTags;
private Bundle mBundle; // lazy-initialized, may be null
+ /** Array of all content types exposed in the SDK that applications can use */
+ private static final IntArray CONTENT_TYPES = IntArray.wrap(new int[]{
+ CONTENT_TYPE_UNKNOWN,
+ CONTENT_TYPE_SPEECH,
+ CONTENT_TYPE_MUSIC,
+ CONTENT_TYPE_MOVIE,
+ CONTENT_TYPE_SONIFICATION,
+ });
+
private AudioAttributes() {
}
@@ -1669,6 +1679,27 @@
}
/**
+ * Query if the usage is a valid sdk usage
+ *
+ * @param usage one of {@link AttributeSdkUsage}
+ * @return {@code true} if the usage is valid for sdk or {@code false} otherwise
+ * @hide
+ */
+ public static boolean isSdkUsage(@AttributeSdkUsage int usage) {
+ return SDK_USAGES.contains(usage);
+ }
+
+ /**
+ * Query if the content type is a valid sdk content type
+ * @param contentType one of {@link AttributeContentType}
+ * @return {@code true} if the content type is valid for sdk or {@code false} otherwise
+ * @hide
+ */
+ public static boolean isSdkContentType(@AttributeContentType int contentType) {
+ return CONTENT_TYPES.contains(contentType);
+ }
+
+ /**
* Returns the stream type matching this {@code AudioAttributes} instance for volume control.
* Use this method to derive the stream type needed to configure the volume
* control slider in an {@link android.app.Activity} with
diff --git a/media/java/android/media/FadeManagerConfiguration.aidl b/media/java/android/media/FadeManagerConfiguration.aidl
new file mode 100644
index 0000000..ceb4ded
--- /dev/null
+++ b/media/java/android/media/FadeManagerConfiguration.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * Class to encapsulate fade configurations.
+ * @hide
+ */
+parcelable FadeManagerConfiguration;
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
new file mode 100644
index 0000000..337d4b0
--- /dev/null
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -0,0 +1,1684 @@
+/*
+ * 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 com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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>
+ * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy
+ *
+ * @hide
+ */
+
+@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;
+
+ /**
+ * Defines the enabled state with Automotive specific configurations
+ */
+ public static final int FADE_STATE_ENABLED_AUTO = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "FADE_STATE", value = {
+ FADE_STATE_DISABLED,
+ FADE_STATE_ENABLED_DEFAULT,
+ FADE_STATE_ENABLED_AUTO,
+ })
+ 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 long DURATION_NOT_SET = 0;
+ /** 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 long mFadeOutDurationMillis;
+ /** fade in duration from builder - used for creating default fade in volume shaper */
+ private final long mFadeInDurationMillis;
+ /** delay after which the offending players are faded back in */
+ private final long mFadeInDelayForOffendersMillis;
+
+ private FadeManagerConfiguration(int fadeState, long fadeOutDurationMillis,
+ long fadeInDurationMillis, 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
+ *
+ * @return one of the {@link FadeStateEnum} state
+ */
+ @FadeStateEnum
+ public int getFadeState() {
+ return mFadeState;
+ }
+
+ /**
+ * Get the list of usages that can be faded
+ *
+ * @return list of {@link android.media.AudioAttributes.AttributeUsage} 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.PlayerType player types}
+ * that cannot be faded
+ *
+ * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
+ * @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.AttributeContentType content types}
+ * that cannot be faded
+ *
+ * @return list of {@link android.media.AudioAttributes.AttributeContentType}
+ * @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.AttributeUsage}
+ *
+ * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @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}
+ */
+ public long getFadeOutDurationForUsage(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.AttributeUsage}
+ *
+ * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @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}
+ */
+ public long getFadeInDurationForUsage(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.AttributeUsage}
+ *
+ * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @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(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.AttributeUsage}
+ *
+ * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of player
+ * @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(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}
+ */
+ 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}
+ */
+ 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
+ *
+ * @return delay in milliseconds
+ */
+ 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.AttributeUsage}
+ * @return {@code true} if usage is fadeable, {@code false} otherwise
+ */
+ 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.AttributeContentType}
+ * @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(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);
+ }
+
+ @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 the fade state in {@link FadeStateEnum}
+ * @return human-readable string
+ */
+ @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";
+ case FADE_STATE_ENABLED_AUTO:
+ return "FADE_STATE_ENABLED_AUTO";
+ 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);
+ }
+
+ 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 enabled, 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 configs 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;
+
+ /** duration of the fade out curve */
+ private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ /** duration of the fade in curve */
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+
+ /**
+ * 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 long mFadeInDelayForOffendersMillis = DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ private long mFadeOutDurationMillis;
+ private 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 default fade out and fade in 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(long fadeOutDurationMillis, 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;
+ mUsageToFadeWrapperMap = fmc.mUsageToFadeWrapperMap.clone();
+ 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 FadeStateEnum} 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.AttributeUsage}
+ * <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.AttributeUsage} 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(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.AttributeUsage}
+ * <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.AttributeUsage}
+ * @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(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.AttributeUsage}
+ * <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.AttributeUsage} 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(int usage, 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.AttributeUsage}
+ * <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.AttributeUsage} 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(int usage, 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
+ * @throws NullPointerException if the audio attributes is {@code null}
+ * @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,
+ 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,
+ 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.AttributeUsage} 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>Passing an empty list as input clears the existing list. This can be used to
+ * reset the list when using a copy constructor
+ *
+ * <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.AttributeUsage}
+ * @return the same Builder instance
+ * @throws IllegalArgumentException if the usages are invalid
+ * @throws NullPointerException if the usage list is {@code null}
+ * @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.AttributeUsage} to the fadeable list
+ *
+ * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @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;
+ }
+
+ /**
+ * Remove the {@link android.media.AudioAttributes.AttributeUsage} from the fadeable list
+ * <p>
+ * Players of this usage type will not be faded.
+ *
+ * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+ * @return the same Builder instance
+ * @throws IllegalArgumentException if the usage is invalid
+ * @see #getFadeableUsages()
+ * @see #setFadeableUsages(List)
+ */
+ @NonNull
+ public Builder clearFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
+ validateUsage(usage);
+ setFlag(IS_FADEABLE_USAGES_FIELD_SET);
+ int index = mFadeableUsages.indexOf(usage);
+ if (index != INVALID_INDEX) {
+ mFadeableUsages.remove(index);
+ }
+ return this;
+ }
+
+ /**
+ * Set the list of {@link android.media.AudioAttributes.AttributeContentType} 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.AttributeContentType}
+ * @return the same Builder instance
+ * @throws IllegalArgumentException if the content types are invalid
+ * @throws NullPointerException if the content type list is {@code null}
+ * @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.AttributeContentType} to unfadeable list
+ *
+ * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+ * @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;
+ }
+
+ /**
+ * Remove the {@link android.media.AudioAttributes.AttributeContentType} from the
+ * unfadeable list
+ *
+ * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+ * @return the same Builder instance
+ * @throws IllegalArgumentException if the content type is invalid
+ * @see #setUnfadeableContentTypes(List)
+ * @see #getUnfadeableContentTypes()
+ */
+ @NonNull
+ public Builder clearUnfadeableContentType(
+ @AudioAttributes.AttributeContentType int contentType) {
+ validateContentType(contentType);
+ setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
+ int index = mUnfadeableContentTypes.indexOf(contentType);
+ if (index != INVALID_INDEX) {
+ mUnfadeableContentTypes.remove(index);
+ }
+ 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
+ *
+ * <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 uids list of uids
+ * @return the same Builder instance
+ * @throws NullPointerException if the uid list is {@code null}
+ * @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;
+ }
+
+ /**
+ * Remove the uid from unfadeable list
+ *
+ * @param uid client uid
+ * @return the same Builder instance
+ * @see #setUnfadeableUids(List)
+ * @see #getUnfadeableUids()
+ */
+ @NonNull
+ public Builder clearUnfadeableUid(int uid) {
+ int index = mUnfadeableUids.indexOf(uid);
+ if (index != INVALID_INDEX) {
+ mUnfadeableUids.remove(index);
+ }
+ 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>Passing an empty list as input clears any existing list. This can be used to
+ * reset the list when using a copy constructor
+ *
+ * <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.AttributeUsage} 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
+ * @throws NullPointerException if the audio attributes list is {@code null}
+ * @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
+ * @throws NullPointerException if the audio attributes is {@code null}
+ * @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;
+ }
+
+ /**
+ * Remove the {@link android.media.AudioAttributes} from the unfadeable list.
+ *
+ * @param audioAttributes the {@link android.media.AudioAttributes}
+ * @return the same Builder instance
+ * @throws NullPointerException if the audio attributes is {@code null}
+ * @see #getUnfadeableAudioAttributes()
+ */
+ @NonNull
+ public Builder clearUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
+ Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
+ if (mUnfadeableAudioAttributes.contains(audioAttributes)) {
+ mUnfadeableAudioAttributes.remove(audioAttributes);
+ }
+ 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(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 validateFadeState(int state) {
+ switch(state) {
+ case FADE_STATE_DISABLED:
+ case FADE_STATE_ENABLED_DEFAULT:
+ case FADE_STATE_ENABLED_AUTO:
+ 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() {}
+
+ 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];
+ }
+ };
+ }
+}
+
diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig
new file mode 100644
index 0000000..100e2235
--- /dev/null
+++ b/media/java/android/media/flags/fade_manager_configuration.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.flags"
+
+flag {
+ namespace: "media_solutions"
+ name: "enable_fade_manager_configuration"
+ description: "Enable Fade Manager Configuration support to determine fade properties"
+ bug: "307354764"
+}
\ No newline at end of file
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 4624dfe..3dc2a0a 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -17,6 +17,7 @@
"guava-android-testlib",
"hamcrest-library",
"platform-test-annotations",
+ "truth",
],
platform_apis: true,
certificate: "platform",
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
index 94df40d..e9a0d3e 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
@@ -229,7 +229,7 @@
@Test
public void testSetGetVolumePerAttributes() {
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.getSdkUsages()) {
if (usage == AudioAttributes.USAGE_UNKNOWN) {
continue;
}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
index 266faae..18e8608 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
@@ -169,7 +169,7 @@
assertNotNull(audioProductStrategies);
assertTrue(audioProductStrategies.size() > 0);
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.getSdkUsages()) {
AudioAttributes aaForUsage = new AudioAttributes.Builder().setUsage(usage).build();
int streamTypeFromUsage =
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
new file mode 100644
index 0000000..fb6bd48
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -0,0 +1,795 @@
+/*
+ * 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 com.android.audiopolicytest;
+
+import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
+import static org.junit.Assert.assertThrows;
+
+import android.media.AudioAttributes;
+import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
+import android.media.VolumeShaper;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+public final class FadeManagerConfigurationUnitTest {
+ private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+ private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
+ private static final long TEST_FADE_IN_DURATION_MS = 750;
+ private static final int TEST_INVALID_USAGE = -10;
+ private static final int TEST_INVALID_CONTENT_TYPE = 100;
+ private static final int TEST_INVALID_FADE_STATE = 100;
+ private static final long TEST_INVALID_DURATION = -10;
+ private static final int TEST_UID_1 = 1010001;
+ private static final int TEST_UID_2 = 1000;
+ private static final int TEST_PARCEL_FLAGS = 0;
+ private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
+ createAudioAttributesForUsage(AudioAttributes.USAGE_MEDIA);
+ private static final AudioAttributes TEST_GAME_AUDIO_ATTRIBUTE =
+ createAudioAttributesForUsage(AudioAttributes.USAGE_GAME);
+ private static final AudioAttributes TEST_NAVIGATION_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+ private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ASSISTANT)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build();
+ private static final List<Integer> TEST_FADEABLE_USAGES = Arrays.asList(
+ AudioAttributes.USAGE_MEDIA,
+ AudioAttributes.USAGE_GAME
+ );
+ private static final List<Integer> TEST_UNFADEABLE_CONTENT_TYPES = Arrays.asList(
+ AudioAttributes.CONTENT_TYPE_SPEECH
+ );
+
+ private static final List<Integer> TEST_UNFADEABLE_PLAYER_TYPES = Arrays.asList(
+ AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
+ AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+ );
+ private static final VolumeShaper.Configuration TEST_DEFAULT_FADE_OUT_VOLUME_SHAPER_CONFIG =
+ new VolumeShaper.Configuration.Builder()
+ .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+ .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f},
+ /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(DEFAULT_FADE_OUT_DURATION_MS)
+ .build();
+ private static final VolumeShaper.Configuration TEST_DEFAULT_FADE_IN_VOLUME_SHAPER_CONFIG =
+ new VolumeShaper.Configuration.Builder()
+ .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+ .setCurve(/* times= */new float[]{0.f, 0.50f, 1.0f},
+ /* volumes= */new float[]{0.f, 0.30f, 1.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(DEFAULT_FADE_IN_DURATION_MS)
+ .build();
+ private static final VolumeShaper.Configuration TEST_FADE_OUT_VOLUME_SHAPER_CONFIG =
+ new VolumeShaper.Configuration.Builder()
+ .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+ .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f},
+ /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(TEST_FADE_OUT_DURATION_MS)
+ .build();
+ private static final VolumeShaper.Configuration TEST_FADE_IN_VOLUME_SHAPER_CONFIG =
+ new VolumeShaper.Configuration.Builder()
+ .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+ .setCurve(/* times= */new float[]{0.f, 0.50f, 1.0f},
+ /* volumes= */new float[]{0.f, 0.30f, 1.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(TEST_FADE_IN_DURATION_MS)
+ .build();
+
+ private FadeManagerConfiguration mFmc;
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Before
+ public void setUp() {
+ mFmc = new FadeManagerConfiguration.Builder().build();
+ }
+
+
+ @Test
+ public void build() {
+ expect.withMessage("Fade state for default builder")
+ .that(mFmc.getFadeState())
+ .isEqualTo(FadeManagerConfiguration.FADE_STATE_ENABLED_DEFAULT);
+ expect.withMessage("Fadeable usages for default builder")
+ .that(mFmc.getFadeableUsages())
+ .containsExactlyElementsIn(TEST_FADEABLE_USAGES);
+ expect.withMessage("Unfadeable content types usages for default builder")
+ .that(mFmc.getUnfadeableContentTypes())
+ .containsExactlyElementsIn(TEST_UNFADEABLE_CONTENT_TYPES);
+ expect.withMessage("Unfadeable player types for default builder")
+ .that(mFmc.getUnfadeablePlayerTypes())
+ .containsExactlyElementsIn(TEST_UNFADEABLE_PLAYER_TYPES);
+ expect.withMessage("Unfadeable uids for default builder")
+ .that(mFmc.getUnfadeableUids()).isEmpty();
+ expect.withMessage("Unfadeable audio attributes for default builder")
+ .that(mFmc.getUnfadeableAudioAttributes()).isEmpty();
+ expect.withMessage("Fade out volume shaper config for media usage")
+ .that(mFmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_DEFAULT_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade out duration for game usage")
+ .that(mFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_GAME))
+ .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade in volume shaper config for media uasge")
+ .that(mFmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_DEFAULT_FADE_IN_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade in duration for game audio usage")
+ .that(mFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_GAME))
+ .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+ }
+
+ @Test
+ public void build_withFadeDurations_succeeds() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration
+ .Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build();
+
+ expect.withMessage("Fade state for builder with duration").that(fmc.getFadeState())
+ .isEqualTo(FadeManagerConfiguration.FADE_STATE_ENABLED_DEFAULT);
+ expect.withMessage("Fadeable usages for builder with duration")
+ .that(fmc.getFadeableUsages())
+ .containsExactlyElementsIn(TEST_FADEABLE_USAGES);
+ expect.withMessage("Unfadeable content types usages for builder with duration")
+ .that(fmc.getUnfadeableContentTypes())
+ .containsExactlyElementsIn(TEST_UNFADEABLE_CONTENT_TYPES);
+ expect.withMessage("Unfadeable player types for builder with duration")
+ .that(fmc.getUnfadeablePlayerTypes())
+ .containsExactlyElementsIn(TEST_UNFADEABLE_PLAYER_TYPES);
+ expect.withMessage("Unfadeable uids for builder with duration")
+ .that(fmc.getUnfadeableUids()).isEmpty();
+ expect.withMessage("Unfadeable audio attributes for builder with duration")
+ .that(fmc.getUnfadeableAudioAttributes()).isEmpty();
+ expect.withMessage("Fade out volume shaper config for media usage")
+ .that(fmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade out duration for game usage")
+ .that(fmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_GAME))
+ .isEqualTo(TEST_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade in volume shaper config for media audio attributes")
+ .that(fmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade in duration for game audio attributes")
+ .that(fmc.getFadeInDurationForUsage(AudioAttributes.USAGE_GAME))
+ .isEqualTo(TEST_FADE_IN_DURATION_MS);
+
+ }
+
+ @Test
+ public void build_withFadeManagerConfiguration_succeeds() {
+ FadeManagerConfiguration fmcObj = new FadeManagerConfiguration
+ .Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build();
+
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration
+ .Builder(fmcObj).build();
+
+ expect.withMessage("Fade state for copy builder").that(fmc.getFadeState())
+ .isEqualTo(fmcObj.getFadeState());
+ expect.withMessage("Fadeable usages for copy builder")
+ .that(fmc.getFadeableUsages())
+ .containsExactlyElementsIn(fmcObj.getFadeableUsages());
+ expect.withMessage("Unfadeable content types usages for copy builder")
+ .that(fmc.getUnfadeableContentTypes())
+ .containsExactlyElementsIn(fmcObj.getUnfadeableContentTypes());
+ expect.withMessage("Unfadeable player types for copy builder")
+ .that(fmc.getUnfadeablePlayerTypes())
+ .containsExactlyElementsIn(fmcObj.getUnfadeablePlayerTypes());
+ expect.withMessage("Unfadeable uids for copy builder")
+ .that(fmc.getUnfadeableUids()).isEqualTo(fmcObj.getUnfadeableUids());
+ expect.withMessage("Unfadeable audio attributes for copy builder")
+ .that(fmc.getUnfadeableAudioAttributes())
+ .isEqualTo(fmcObj.getUnfadeableAudioAttributes());
+ expect.withMessage("Fade out volume shaper config for media usage")
+ .that(fmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(fmcObj.getFadeOutVolumeShaperConfigForUsage(
+ AudioAttributes.USAGE_MEDIA));
+ expect.withMessage("Fade out volume shaper config for game usage")
+ .that(fmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_GAME))
+ .isEqualTo(fmcObj.getFadeOutVolumeShaperConfigForUsage(
+ AudioAttributes.USAGE_GAME));
+ expect.withMessage("Fade in volume shaper config for media usage")
+ .that(fmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(fmcObj.getFadeInVolumeShaperConfigForUsage(
+ AudioAttributes.USAGE_MEDIA));
+ expect.withMessage("Fade in volume shaper config for game usage")
+ .that(fmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_GAME))
+ .isEqualTo(fmcObj.getFadeInVolumeShaperConfigForUsage(
+ AudioAttributes.USAGE_GAME));
+ expect.withMessage("Fade out volume shaper config for media audio attributes")
+ .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
+ TEST_MEDIA_AUDIO_ATTRIBUTE))
+ .isEqualTo(fmcObj.getFadeOutVolumeShaperConfigForAudioAttributes(
+ TEST_MEDIA_AUDIO_ATTRIBUTE));
+ expect.withMessage("Fade out duration for game audio attributes")
+ .that(fmc.getFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+ .isEqualTo(fmcObj.getFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE));
+ expect.withMessage("Fade in volume shaper config for media audio attributes")
+ .that(fmc.getFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE))
+ .isEqualTo(fmcObj.getFadeInVolumeShaperConfigForAudioAttributes(
+ TEST_MEDIA_AUDIO_ATTRIBUTE));
+ expect.withMessage("Fade in duration for game audio attributes")
+ .that(fmc.getFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+ .isEqualTo(fmcObj.getFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE));
+ }
+
+ @Test
+ public void testSetFadeState_toDisable() {
+ final int fadeState = FadeManagerConfiguration.FADE_STATE_DISABLED;
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeState(fadeState).build();
+
+ expect.withMessage("Fade state when disabled").that(fmc.getFadeState())
+ .isEqualTo(fadeState);
+ }
+
+ @Test
+ public void testSetFadeState_toEnableAuto() {
+ final int fadeStateAuto = FadeManagerConfiguration.FADE_STATE_ENABLED_AUTO;
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeState(fadeStateAuto).build();
+
+ expect.withMessage("Fade state when enabled for audio").that(fmc.getFadeState())
+ .isEqualTo(fadeStateAuto);
+ }
+
+ @Test
+ public void testSetFadeState_toInvalid_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setFadeState(TEST_INVALID_FADE_STATE).build()
+ );
+
+ expect.withMessage("Invalid fade state exception").that(thrown)
+ .hasMessageThat().contains("Unknown fade state");
+ }
+
+ @Test
+ public void testSetFadeVolShaperConfig() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+ expect.withMessage("Fade out volume shaper config set for assistant audio attributes")
+ .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
+ TEST_ASSISTANT_AUDIO_ATTRIBUTE))
+ .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+ expect.withMessage("Fade in volume shaper config set for assistant audio attributes")
+ .that(fmc.getFadeInVolumeShaperConfigForAudioAttributes(
+ TEST_ASSISTANT_AUDIO_ATTRIBUTE))
+ .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+ }
+
+ @Test
+ public void testSetFadeOutVolShaperConfig_withNullAudioAttributes_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setFadeOutVolumeShaperConfigForAudioAttributes(/* audioAttributes= */ null,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG).build()
+ );
+
+ expect.withMessage("Null audio attributes for fade out exception")
+ .that(thrown).hasMessageThat().contains("cannot be null");
+ }
+
+ @Test
+ public void testSetFadeVolShaperConfig_withNullVolumeShaper_getsNull() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(mFmc)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+ /* VolumeShaper.Configuration= */ null)
+ .setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+ /* VolumeShaper.Configuration= */ null)
+ .clearFadeableUsage(AudioAttributes.USAGE_MEDIA).build();
+
+ expect.withMessage("Fade out volume shaper config set with null value")
+ .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
+ TEST_MEDIA_AUDIO_ATTRIBUTE)).isNull();
+ }
+
+ @Test
+ public void testSetFadeInVolShaperConfig_withNullAudioAttributes_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setFadeInVolumeShaperConfigForAudioAttributes(/* audioAttributes= */ null,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build()
+ );
+
+ expect.withMessage("Null audio attributes for fade in exception")
+ .that(thrown).hasMessageThat().contains("cannot be null");
+ }
+
+ @Test
+ public void testSetFadeDuration() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE,
+ TEST_FADE_OUT_DURATION_MS)
+ .setFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE,
+ TEST_FADE_IN_DURATION_MS).build();
+
+ expect.withMessage("Fade out duration set for audio attributes")
+ .that(fmc.getFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+ .isEqualTo(TEST_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade in duration set for audio attributes")
+ .that(fmc.getFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+ .isEqualTo(TEST_FADE_IN_DURATION_MS);
+ }
+
+ @Test
+ public void testSetFadeOutDuration_withNullAudioAttributes_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder().setFadeOutDurationForAudioAttributes(
+ /* audioAttributes= */ null, TEST_FADE_OUT_DURATION_MS).build()
+ );
+
+ expect.withMessage("Null audio attributes for fade out duration exception").that(thrown)
+ .hasMessageThat().contains("cannot be null");
+ }
+
+ @Test
+ public void testSetFadeOutDuration_withInvalidDuration_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new FadeManagerConfiguration.Builder().setFadeOutDurationForAudioAttributes(
+ TEST_NAVIGATION_AUDIO_ATTRIBUTE, TEST_INVALID_DURATION).build()
+ );
+
+ expect.withMessage("Invalid duration for fade out exception").that(thrown)
+ .hasMessageThat().contains("not positive");
+ }
+
+ @Test
+ public void testSetFadeInDuration_withNullAudioAttributes_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder().setFadeInDurationForAudioAttributes(
+ /* audioAttributes= */ null, TEST_FADE_IN_DURATION_MS).build()
+ );
+
+ expect.withMessage("Null audio attributes for fade in duration exception").that(thrown)
+ .hasMessageThat().contains("cannot be null");
+ }
+
+ @Test
+ public void testSetFadeInDuration_withInvalidDuration_fails() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new FadeManagerConfiguration.Builder().setFadeInDurationForAudioAttributes(
+ TEST_NAVIGATION_AUDIO_ATTRIBUTE, TEST_INVALID_DURATION).build()
+ );
+
+ expect.withMessage("Invalid duration for fade in exception").that(thrown)
+ .hasMessageThat().contains("not positive");
+ }
+
+ @Test
+ public void testSetFadeableUsages() {
+ final List<Integer> fadeableUsages = List.of(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION,
+ AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_ASSISTANT
+ );
+ AudioAttributes aaForVoiceComm = createAudioAttributesForUsage(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION);
+ AudioAttributes aaForAlarm = createAudioAttributesForUsage(AudioAttributes.USAGE_ALARM);
+ AudioAttributes aaForAssistant = createAudioAttributesForUsage(
+ AudioAttributes.USAGE_ASSISTANT);
+
+
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeableUsages(fadeableUsages)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaForAlarm,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaForAlarm,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaForAssistant,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaForAssistant,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+ expect.withMessage("Fadeable usages")
+ .that(fmc.getFadeableUsages()).isEqualTo(fadeableUsages);
+ }
+
+ @Test
+ public void testSetFadeableUsages_withInvalidUsage_fails() {
+ final List<Integer> fadeableUsages = List.of(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION,
+ TEST_INVALID_USAGE,
+ AudioAttributes.USAGE_ANNOUNCEMENT
+ );
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new FadeManagerConfiguration.Builder().setFadeableUsages(fadeableUsages).build()
+ );
+
+ expect.withMessage("Fadeable usages set to invalid usage").that(thrown).hasMessageThat()
+ .contains("Invalid usage");
+ }
+
+ @Test
+ public void testSetFadeableUsages_withNullUsages_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder().setFadeableUsages(/* usages= */ null)
+ .build()
+ );
+
+ expect.withMessage("Fadeable usages set to null list").that(thrown).hasMessageThat()
+ .contains("cannot be null");
+ }
+
+ @Test
+ public void testSetFadeableUsages_withEmptyListClears_addsNewUsage() {
+ final List<Integer> fadeableUsages = List.of(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION,
+ AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_ASSISTANT
+ );
+ FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder()
+ .setFadeableUsages(fadeableUsages);
+
+ fmcBuilder.setFadeableUsages(List.of());
+
+ FadeManagerConfiguration fmc = fmcBuilder
+ .addFadeableUsage(AudioAttributes.USAGE_MEDIA)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+ expect.withMessage("Fadeable usages set to empty list")
+ .that(fmc.getFadeableUsages()).isEqualTo(List.of(AudioAttributes.USAGE_MEDIA));
+ }
+
+
+ @Test
+ public void testAddFadeableUsage() {
+ final int usageToAdd = AudioAttributes.USAGE_ASSISTANT;
+ AudioAttributes aaToAdd = createAudioAttributesForUsage(usageToAdd);
+ List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages());
+ updatedUsages.add(usageToAdd);
+
+ FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
+ .Builder(mFmc).addFadeableUsage(usageToAdd)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaToAdd,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaToAdd,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+ .build();
+
+ expect.withMessage("Fadeable usages").that(updatedFmc.getFadeableUsages())
+ .containsExactlyElementsIn(updatedUsages);
+ }
+
+ @Test
+ public void testAddFadeableUsage_withoutSetFadeableUsages() {
+ final int newUsage = AudioAttributes.USAGE_ASSISTANT;
+ AudioAttributes aaToAdd = createAudioAttributesForUsage(newUsage);
+
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .addFadeableUsage(newUsage)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaToAdd,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaToAdd,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+ .build();
+
+ expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
+ .containsExactlyElementsIn(List.of(newUsage));
+ }
+
+ @Test
+ public void testAddFadeableUsage_withInvalidUsage_fails() {
+ List<Integer> setUsages = Arrays.asList(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION,
+ AudioAttributes.USAGE_ASSISTANT
+ );
+ AudioAttributes aaForVoiceComm = createAudioAttributesForUsage(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION);
+ AudioAttributes aaForAssistant = createAudioAttributesForUsage(
+ AudioAttributes.USAGE_ASSISTANT);
+ FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder()
+ .setFadeableUsages(setUsages)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+ .setFadeOutVolumeShaperConfigForAudioAttributes(aaForAssistant,
+ TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+ .setFadeInVolumeShaperConfigForAudioAttributes(aaForAssistant,
+ TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ fmcBuilder.addFadeableUsage(TEST_INVALID_USAGE)
+ );
+
+ FadeManagerConfiguration fmc = fmcBuilder.build();
+ expect.withMessage("Fadeable usages ").that(thrown).hasMessageThat()
+ .contains("Invalid usage");
+ expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
+ .containsExactlyElementsIn(setUsages);
+ }
+
+ @Test
+ public void testClearFadeableUsage() {
+ final int usageToClear = AudioAttributes.USAGE_MEDIA;
+ List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages());
+ updatedUsages.remove((Integer) usageToClear);
+
+ FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
+ .Builder(mFmc).clearFadeableUsage(usageToClear).build();
+
+ expect.withMessage("Clear fadeable usage").that(updatedFmc.getFadeableUsages())
+ .containsExactlyElementsIn(updatedUsages);
+ }
+
+ @Test
+ public void testClearFadeableUsage_withInvalidUsage_fails() {
+ FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ fmcBuilder.clearFadeableUsage(TEST_INVALID_USAGE)
+ );
+
+ FadeManagerConfiguration fmc = fmcBuilder.build();
+ expect.withMessage("Clear invalid usage").that(thrown).hasMessageThat()
+ .contains("Invalid usage");
+ expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
+ .containsExactlyElementsIn(mFmc.getFadeableUsages());
+ }
+
+ @Test
+ public void testSetUnfadeableContentTypes() {
+ final List<Integer> unfadeableContentTypes = List.of(
+ AudioAttributes.CONTENT_TYPE_MOVIE,
+ AudioAttributes.CONTENT_TYPE_SONIFICATION
+ );
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setUnfadeableContentTypes(unfadeableContentTypes).build();
+
+ expect.withMessage("Unfadeable content types set")
+ .that(fmc.getUnfadeableContentTypes()).isEqualTo(unfadeableContentTypes);
+ }
+
+ @Test
+ public void testSetUnfadeableContentTypes_withInvalidContentType_fails() {
+ final List<Integer> invalidUnfadeableContentTypes = List.of(
+ AudioAttributes.CONTENT_TYPE_MOVIE,
+ TEST_INVALID_CONTENT_TYPE,
+ AudioAttributes.CONTENT_TYPE_SONIFICATION
+ );
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setUnfadeableContentTypes(invalidUnfadeableContentTypes).build()
+ );
+
+ expect.withMessage("Invalid content type set exception").that(thrown).hasMessageThat()
+ .contains("Invalid content type");
+ }
+
+ @Test
+ public void testSetUnfadeableContentTypes_withNullContentType_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setUnfadeableContentTypes(/* contentType= */ null).build()
+ );
+
+ expect.withMessage("Null content type set exception").that(thrown).hasMessageThat()
+ .contains("cannot be null");
+ }
+
+ @Test
+ public void testSetUnfadeableContentTypes_withEmptyList_clearsExistingList() {
+ final List<Integer> unfadeableContentTypes = List.of(
+ AudioAttributes.CONTENT_TYPE_MOVIE,
+ AudioAttributes.CONTENT_TYPE_SONIFICATION
+ );
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setUnfadeableContentTypes(unfadeableContentTypes).build();
+
+ FadeManagerConfiguration fmcWithEmptyLsit = new FadeManagerConfiguration.Builder(fmc)
+ .setUnfadeableContentTypes(List.of()).build();
+
+ expect.withMessage("Unfadeable content types for empty list")
+ .that(fmcWithEmptyLsit.getUnfadeableContentTypes()).isEmpty();
+ }
+
+ @Test
+ public void testAddUnfadeableContentType() {
+ final int contentTypeToAdd = AudioAttributes.CONTENT_TYPE_MOVIE;
+ List<Integer> upatdedContentTypes = new ArrayList<>(mFmc.getUnfadeableContentTypes());
+ upatdedContentTypes.add(contentTypeToAdd);
+
+ FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
+ .Builder(mFmc).addUnfadeableContentType(contentTypeToAdd).build();
+
+ expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes())
+ .containsExactlyElementsIn(upatdedContentTypes);
+ }
+
+ @Test
+ public void testAddUnfadeableContentTypes_withoutSetUnfadeableContentTypes() {
+ final int newContentType = AudioAttributes.CONTENT_TYPE_MOVIE;
+
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .addUnfadeableContentType(newContentType).build();
+
+ expect.withMessage("Unfadeable content types").that(fmc.getUnfadeableContentTypes())
+ .containsExactlyElementsIn(List.of(newContentType));
+ }
+
+ @Test
+ public void testAddunfadeableContentTypes_withInvalidContentType_fails() {
+ final List<Integer> unfadeableContentTypes = List.of(
+ AudioAttributes.CONTENT_TYPE_MOVIE,
+ AudioAttributes.CONTENT_TYPE_SONIFICATION
+ );
+ FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder()
+ .setUnfadeableContentTypes(unfadeableContentTypes);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ fmcBuilder.addUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build()
+ );
+
+ expect.withMessage("Invalid content types exception").that(thrown).hasMessageThat()
+ .contains("Invalid content type");
+ }
+
+ @Test
+ public void testClearUnfadeableContentType() {
+ List<Integer> unfadeableContentTypes = new ArrayList<>(Arrays.asList(
+ AudioAttributes.CONTENT_TYPE_MOVIE,
+ AudioAttributes.CONTENT_TYPE_SONIFICATION
+ ));
+ final int contentTypeToClear = AudioAttributes.CONTENT_TYPE_MOVIE;
+
+ FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder()
+ .setUnfadeableContentTypes(unfadeableContentTypes)
+ .clearUnfadeableContentType(contentTypeToClear).build();
+
+ unfadeableContentTypes.remove((Integer) contentTypeToClear);
+ expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes())
+ .containsExactlyElementsIn(unfadeableContentTypes);
+ }
+
+ @Test
+ public void testClearUnfadeableContentType_withInvalidContentType_fails() {
+ FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+ fmcBuilder.clearUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build()
+ );
+
+ expect.withMessage("Invalid content type exception").that(thrown).hasMessageThat()
+ .contains("Invalid content type");
+ }
+
+ @Test
+ public void testSetUnfadeableUids() {
+ final List<Integer> unfadeableUids = List.of(
+ TEST_UID_1,
+ TEST_UID_2
+ );
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setUnfadeableUids(unfadeableUids).build();
+
+ expect.withMessage("Unfadeable uids set")
+ .that(fmc.getUnfadeableUids()).isEqualTo(unfadeableUids);
+ }
+
+ @Test
+ public void testSetUnfadeableUids_withNullUids_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setUnfadeableUids(/* uids= */ null).build()
+ );
+
+ expect.withMessage("Null unfadeable uids").that(thrown).hasMessageThat()
+ .contains("cannot be null");
+ }
+
+ @Test
+ public void testAddUnfadeableUid() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .addUnfadeableUid(TEST_UID_1).build();
+
+ expect.withMessage("Unfadeable uids")
+ .that(fmc.getUnfadeableUids()).isEqualTo(List.of(TEST_UID_1));
+ }
+
+ @Test
+ public void testClearUnfadebaleUid() {
+ final List<Integer> unfadeableUids = List.of(
+ TEST_UID_1,
+ TEST_UID_2
+ );
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setUnfadeableUids(unfadeableUids).build();
+
+ FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(fmc)
+ .clearUnfadeableUid(TEST_UID_1).build();
+
+ expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids())
+ .isEqualTo(List.of(TEST_UID_2));
+ }
+
+ @Test
+ public void testSetUnfadeableAudioAttributes() {
+ final List<AudioAttributes> unfadeableAttrs = List.of(
+ TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_NAVIGATION_AUDIO_ATTRIBUTE
+ );
+
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setUnfadeableAudioAttributes(unfadeableAttrs).build();
+
+ expect.withMessage("Unfadeable audio attributes")
+ .that(fmc.getUnfadeableAudioAttributes()).isEqualTo(unfadeableAttrs);
+ }
+
+ @Test
+ public void testSetUnfadeableAudioAttributes_withNullAttributes_fails() {
+ NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+ new FadeManagerConfiguration.Builder()
+ .setUnfadeableAudioAttributes(/* attrs= */ null).build()
+ );
+
+ expect.withMessage("Null audio attributes exception").that(thrown).hasMessageThat()
+ .contains("cannot be null");
+ }
+
+ @Test
+ public void testWriteToParcel_andCreateFromParcel() {
+ Parcel parcel = Parcel.obtain();
+
+ mFmc.writeToParcel(parcel, TEST_PARCEL_FLAGS);
+ parcel.setDataPosition(/* position= */ 0);
+ expect.withMessage("Fade manager configuration write to and create from parcel")
+ .that(mFmc)
+ .isEqualTo(FadeManagerConfiguration.CREATOR.createFromParcel(parcel));
+ }
+
+ private static AudioAttributes createAudioAttributesForUsage(int usage) {
+ if (AudioAttributes.isSystemUsage(usage)) {
+ return new AudioAttributes.Builder().setSystemUsage(usage).build();
+ }
+ return new AudioAttributes.Builder().setUsage(usage).build();
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AudioRestrictionManager.java b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
index be87037..b9ccc53 100644
--- a/services/core/java/com/android/server/appop/AudioRestrictionManager.java
+++ b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
@@ -43,7 +43,7 @@
static {
SparseBooleanArray audioMutedUsages = new SparseBooleanArray();
SparseBooleanArray vibrationMutedUsages = new SparseBooleanArray();
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.SDK_USAGES.toArray()) {
final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION ||
suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL ||
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 89d8200..d0ded63 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1519,7 +1519,7 @@
final boolean muteEverything = zenSilence || (zenPriorityOnly
&& ZenModeConfig.areAllZenBehaviorSoundsMuted(mConsolidatedPolicy));
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.SDK_USAGES.toArray()) {
final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NEVER) {
applyRestrictions(zenPriorityOnly, false /*mute*/, usage);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 97b6b98..a44986a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -524,7 +524,7 @@
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.getSdkUsages()) {
if (usage == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) {
// only mute audio, not vibrations
verify(mAppOps, atLeastOnce()).setRestriction(eq(AppOpsManager.OP_PLAY_AUDIO),
@@ -546,7 +546,7 @@
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.getSdkUsages()) {
verify(mAppOps).setRestriction(
eq(AppOpsManager.OP_PLAY_AUDIO), eq(usage), anyInt(), eq(new String[]{PKG_O}));
verify(mAppOps).setRestriction(
@@ -561,7 +561,7 @@
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.getSdkUsages()) {
verify(mAppOps).setRestriction(
eq(AppOpsManager.OP_PLAY_AUDIO), eq(usage), anyInt(), eq(null));
verify(mAppOps).setRestriction(
@@ -576,7 +576,7 @@
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
- for (int usage : AudioAttributes.SDK_USAGES) {
+ for (int usage : AudioAttributes.getSdkUsages()) {
verify(mAppOps).setRestriction(
eq(AppOpsManager.OP_PLAY_AUDIO), eq(usage), anyInt(), eq(null));
verify(mAppOps).setRestriction(