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