summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp5
-rw-r--r--media/java/android/media/AudioAttributes.java37
-rw-r--r--media/java/android/media/FadeManagerConfiguration.aidl23
-rw-r--r--media/java/android/media/FadeManagerConfiguration.java1684
-rw-r--r--media/java/android/media/flags/fade_manager_configuration.aconfig8
-rw-r--r--media/tests/AudioPolicyTest/Android.bp1
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java2
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java2
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java795
-rw-r--r--services/core/java/com/android/server/appop/AudioRestrictionManager.java2
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java8
12 files changed, 2557 insertions, 12 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 941d842d8ec4..0cc4207d6d7b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -425,7 +425,10 @@ java_aconfig_library {
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 bf9419fe6603..4be282b5de87 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -29,6 +29,7 @@ import android.os.Bundle;
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 @@ public final class AudioAttributes implements Parcelable {
* @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 @@ public final class AudioAttributes implements Parcelable {
USAGE_ASSISTANCE_SONIFICATION,
USAGE_GAME,
USAGE_ASSISTANT,
- };
+ });
/**
* @hide
*/
@TestApi
public static int[] getSdkUsages() {
- return SDK_USAGES;
+ return SDK_USAGES.toArray();
}
/**
@@ -567,6 +568,15 @@ public final class AudioAttributes implements Parcelable {
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 @@ public final class AudioAttributes implements Parcelable {
}
/**
+ * 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 000000000000..ceb4ded76dcf
--- /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 000000000000..337d4b0a916c
--- /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 000000000000..100e2235a7a8
--- /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 4624dfe70756..3dc2a0a9fd7c 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -17,6 +17,7 @@ android_test {
"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 94df40da16c1..e9a0d3eceba3 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 @@ public class AudioManagerTest {
@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 266faae489dd..18e8608d3b4d 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 @@ public class AudioProductStrategyTest {
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 000000000000..fb6bd489d5d0
--- /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 be870373af63..b9ccc5389337 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 @@ public class AudioRestrictionManager {
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 89d820050b03..d0ded63162db 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 @@ public class ZenModeHelper {
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 97b6b98a0b08..a44986af88a8 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 @@ public class ZenModeHelperTest extends UiServiceTestCase {
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 @@ public class ZenModeHelperTest extends UiServiceTestCase {
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 @@ public class ZenModeHelperTest extends UiServiceTestCase {
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 @@ public class ZenModeHelperTest extends UiServiceTestCase {
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(