1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
20 
21 import android.annotation.DurationMillisLong;
22 import android.annotation.FlaggedApi;
23 import android.annotation.IntDef;
24 import android.annotation.IntRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.SystemApi;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.ArrayMap;
31 import android.util.IntArray;
32 import android.util.SparseArray;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.util.Preconditions;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.Objects;
43 
44 /**
45  * Class to encapsulate fade configurations.
46  *
47  * <p>Configurations are provided through:
48  * <ul>
49  *     <li>Fadeable list: a positive list of fadeable type - usage</li>
50  *     <li>Unfadeable lists: negative list of unfadeable types - content type, uid, audio attributes
51  *     </li>
52  *     <li>Volume shaper configs: fade in and fade out configs per usage or audio attributes
53  *     </li>
54  * </ul>
55  *
56  * <p>Fade manager configuration can be created in one of the following ways:
57  * <ul>
58  *     <li>Disabled fades:
59  *     <pre class="prettyprint">
60  *         new FadeManagerConfiguration.Builder()
61  *               .setFadeState(FADE_STATE_DISABLED).build()
62  *               </pre>
63  *     Can be used to disable fading</li>
64  *     <li>Default configurations including default fade duration:
65  *     <pre class="prettyprint">
66  *         new FadeManagerConfiguration.Builder()
67  *                .setFadeState(FADE_STATE_ENABLED_DEFAULT).build()
68  *                </pre>
69  *     Can be used to enable default fading configurations</li>
70  *     <li>Default configurations with custom fade duration:
71  *     <pre class="prettyprint">
72  *         new FadeManagerConfiguration.Builder(fade out duration, fade in duration)
73  *            .setFadeState(FADE_STATE_ENABLED_DEFAULT).build()
74  *            </pre>
75  *     Can be used to enable default fadeability lists with configurable fade in and out duration
76  *     </li>
77  *     <li>Custom configurations and fade volume shapers:
78  *     <pre class="prettyprint">
79  *         new FadeManagerConfiguration.Builder(fade out duration, fade in duration)
80  *                .setFadeState(FADE_STATE_ENABLED_DEFAULT)
81  *                .setFadeableUsages(list of usages)
82  *                .setUnfadeableContentTypes(list of content types)
83  *                .setUnfadeableUids(list of uids)
84  *                .setUnfadeableAudioAttributes(list of audio attributes)
85  *                .setFadeOutVolumeShaperConfigForAudioAttributes(attributes, volume shaper config)
86  *                .setFadeInDurationForUsaeg(usage, duration)
87  *                ....
88  *                .build() </pre>
89  *      Achieves full customization of fadeability lists and configurations</li>
90  *      <li>Also provides a copy constructor from another instance of fade manager configuration
91  *      <pre class="prettyprint">
92  *          new FadeManagerConfiguration.Builder(fadeManagerConfiguration)
93  *                 .addFadeableUsage(new usage)
94  *                 ....
95  *                 .build()</pre>
96  *      Helps with recreating a new instance from another to simply change/add on top of the
97  *      existing ones</li>
98  * </ul>
99  * @hide
100  */
101 @SystemApi
102 @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
103 public final class FadeManagerConfiguration implements Parcelable {
104 
105     public static final String TAG = "FadeManagerConfiguration";
106 
107     /**
108      * Defines the disabled fade state. No player will be faded in this state.
109      */
110     public static final int FADE_STATE_DISABLED = 0;
111 
112     /**
113      * Defines the enabled fade state with default configurations
114      */
115     public static final int FADE_STATE_ENABLED_DEFAULT = 1;
116 
117     /** @hide */
118     @Retention(RetentionPolicy.SOURCE)
119     @IntDef(flag = false, prefix = "FADE_STATE", value = {
120             FADE_STATE_DISABLED,
121             FADE_STATE_ENABLED_DEFAULT,
122     })
123     public @interface FadeStateEnum {}
124 
125     /**
126      * Defines ID to be used in volume shaper for fading
127      */
128     public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2;
129 
130     /**
131      * Used to reset duration or return duration when not set
132      *
133      * @see Builder#setFadeOutDurationForUsage(int, long)
134      * @see Builder#setFadeInDurationForUsage(int, long)
135      * @see Builder#setFadeOutDurationForAudioAttributes(AudioAttributes, long)
136      * @see Builder#setFadeInDurationForAudioAttributes(AudioAttributes, long)
137      * @see #getFadeOutDurationForUsage(int)
138      * @see #getFadeInDurationForUsage(int)
139      * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
140      * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
141      */
142     public static final @DurationMillisLong long DURATION_NOT_SET = 0;
143 
144     /** Defines the default fade out duration */
145     private static final @DurationMillisLong long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
146 
147     /** Defines the default fade in duration */
148     private static final @DurationMillisLong long DEFAULT_FADE_IN_DURATION_MS = 1_000;
149 
150     /** Map of Usage to Fade volume shaper configs wrapper */
151     private final SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap;
152     /** Map of AudioAttributes to Fade volume shaper configs wrapper */
153     private final ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap;
154     /** list of fadeable usages */
155     private final @NonNull IntArray mFadeableUsages;
156     /** list of unfadeable content types */
157     private final @NonNull IntArray mUnfadeableContentTypes;
158     /** list of unfadeable player types */
159     private final @NonNull IntArray mUnfadeablePlayerTypes;
160     /** list of unfadeable uid(s) */
161     private final @NonNull IntArray mUnfadeableUids;
162     /** list of unfadeable AudioAttributes */
163     private final @NonNull List<AudioAttributes> mUnfadeableAudioAttributes;
164     /** fade state */
165     private final @FadeStateEnum int mFadeState;
166     /** fade out duration from builder - used for creating default fade out volume shaper */
167     private final @DurationMillisLong long mFadeOutDurationMillis;
168     /** fade in duration from builder - used for creating default fade in volume shaper */
169     private final @DurationMillisLong long mFadeInDurationMillis;
170     /** delay after which the offending players are faded back in */
171     private final @DurationMillisLong long mFadeInDelayForOffendersMillis;
172 
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)173     private FadeManagerConfiguration(int fadeState, @DurationMillisLong long fadeOutDurationMillis,
174             @DurationMillisLong long fadeInDurationMillis,
175             @DurationMillisLong long offendersFadeInDelayMillis,
176             @NonNull SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap,
177             @NonNull ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap,
178             @NonNull IntArray fadeableUsages, @NonNull IntArray unfadeableContentTypes,
179             @NonNull IntArray unfadeablePlayerTypes, @NonNull IntArray unfadeableUids,
180             @NonNull List<AudioAttributes> unfadeableAudioAttributes) {
181         mFadeState = fadeState;
182         mFadeOutDurationMillis = fadeOutDurationMillis;
183         mFadeInDurationMillis = fadeInDurationMillis;
184         mFadeInDelayForOffendersMillis = offendersFadeInDelayMillis;
185         mUsageToFadeWrapperMap = Objects.requireNonNull(usageToFadeWrapperMap,
186                 "Usage to fade wrapper map cannot be null");
187         mAttrToFadeWrapperMap = Objects.requireNonNull(attrToFadeWrapperMap,
188                 "Attribute to fade wrapper map cannot be null");
189         mFadeableUsages = Objects.requireNonNull(fadeableUsages,
190                 "List of fadeable usages cannot be null");
191         mUnfadeableContentTypes = Objects.requireNonNull(unfadeableContentTypes,
192                 "List of unfadeable content types cannot be null");
193         mUnfadeablePlayerTypes = Objects.requireNonNull(unfadeablePlayerTypes,
194                 "List of unfadeable player types cannot be null");
195         mUnfadeableUids = Objects.requireNonNull(unfadeableUids,
196                 "List of unfadeable uids cannot be null");
197         mUnfadeableAudioAttributes = Objects.requireNonNull(unfadeableAudioAttributes,
198                 "List of unfadeable audio attributes cannot be null");
199     }
200 
201     /**
202      * Get the fade state
203      */
204     @FadeStateEnum
getFadeState()205     public int getFadeState() {
206         return mFadeState;
207     }
208 
209     /**
210      * Get the list of usages that can be faded
211      *
212      * @return list of {@link android.media.AudioAttributes usages} that shall be faded
213      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
214      */
215     @NonNull
getFadeableUsages()216     public List<Integer> getFadeableUsages() {
217         ensureFadingIsEnabled();
218         return convertIntArrayToIntegerList(mFadeableUsages);
219     }
220 
221     /**
222      * Get the list of {@link android.media.AudioPlaybackConfiguration player types} that can be
223      * faded
224      *
225      * @return list of {@link android.media.AudioPlaybackConfiguration player types}
226      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
227      */
228     @NonNull
getUnfadeablePlayerTypes()229     public List<Integer> getUnfadeablePlayerTypes() {
230         ensureFadingIsEnabled();
231         return convertIntArrayToIntegerList(mUnfadeablePlayerTypes);
232     }
233 
234     /**
235      * Get the list of {@link android.media.AudioAttributes content types} that can be faded
236      *
237      * @return list of {@link android.media.AudioAttributes content types}
238      * @throws IllegalStateExceptionif if the fade state is set to {@link #FADE_STATE_DISABLED}
239      */
240     @NonNull
getUnfadeableContentTypes()241     public List<Integer> getUnfadeableContentTypes() {
242         ensureFadingIsEnabled();
243         return convertIntArrayToIntegerList(mUnfadeableContentTypes);
244     }
245 
246     /**
247      * Get the list of uids that cannot be faded
248      *
249      * @return list of uids that shall not be faded
250      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
251      */
252     @NonNull
getUnfadeableUids()253     public List<Integer> getUnfadeableUids() {
254         ensureFadingIsEnabled();
255         return convertIntArrayToIntegerList(mUnfadeableUids);
256     }
257 
258     /**
259      * Get the list of {@link android.media.AudioAttributes} that cannot be faded
260      *
261      * @return list of {@link android.media.AudioAttributes} that shall not be faded
262      * @throws IllegalStateException if fade state is set to {@link #FADE_STATE_DISABLED}
263      */
264     @NonNull
getUnfadeableAudioAttributes()265     public List<AudioAttributes> getUnfadeableAudioAttributes() {
266         ensureFadingIsEnabled();
267         return mUnfadeableAudioAttributes;
268     }
269 
270     /**
271      * Get the duration used to fade out players with {@link android.media.AudioAttributes usage}
272      *
273      * @param usage the {@link android.media.AudioAttributes usage}
274      * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
275      * @throws IllegalArgumentException if the usage is invalid
276      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
277      */
278     @IntRange(from = 0) @DurationMillisLong
getFadeOutDurationForUsage(@udioAttributes.AttributeUsage int usage)279     public long getFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
280         ensureFadingIsEnabled();
281         validateUsage(usage);
282         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
283                 mUsageToFadeWrapperMap.get(usage), /* isFadeIn= */ false));
284     }
285 
286     /**
287      * Get the duration used to fade in players with {@link android.media.AudioAttributes usage}
288      *
289      * @param usage the {@link android.media.AudioAttributes usage}
290      * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
291      * @throws IllegalArgumentException if the usage is invalid
292      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
293      */
294     @IntRange(from = 0) @DurationMillisLong
getFadeInDurationForUsage(@udioAttributes.AttributeUsage int usage)295     public long getFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
296         ensureFadingIsEnabled();
297         validateUsage(usage);
298         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
299                 mUsageToFadeWrapperMap.get(usage), /* isFadeIn= */ true));
300     }
301 
302     /**
303      * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
304      * {@link android.media.AudioAttributes usage}
305      *
306      * @param usage the {@link android.media.AudioAttributes usage}
307      * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
308      *     {@code null} otherwise
309      * @throws IllegalArgumentException if the usage is invalid
310      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
311      */
312     @Nullable
getFadeOutVolumeShaperConfigForUsage( @udioAttributes.AttributeUsage int usage)313     public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(
314             @AudioAttributes.AttributeUsage int usage) {
315         ensureFadingIsEnabled();
316         validateUsage(usage);
317         return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
318                 /* isFadeIn= */ false);
319     }
320 
321     /**
322      * Get the {@link android.media.VolumeShaper.Configuration} used to fade in players with
323      * {@link android.media.AudioAttributes usage}
324      *
325      * @param usage the {@link android.media.AudioAttributes usage}
326      * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
327      *     {@code null} otherwise
328      * @throws IllegalArgumentException if the usage is invalid
329      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
330      */
331     @Nullable
getFadeInVolumeShaperConfigForUsage( @udioAttributes.AttributeUsage int usage)332     public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(
333             @AudioAttributes.AttributeUsage int usage) {
334         ensureFadingIsEnabled();
335         validateUsage(usage);
336         return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
337                 /* isFadeIn= */ true);
338     }
339 
340     /**
341      * Get the duration used to fade out players with {@link android.media.AudioAttributes}
342      *
343      * @param audioAttributes {@link android.media.AudioAttributes}
344      * @return duration in milliseconds if set for the audio attributes or
345      *     {@link #DURATION_NOT_SET} otherwise
346      * @throws NullPointerException if the audio attributes is {@code null}
347      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
348      */
349     @IntRange(from = 0) @DurationMillisLong
getFadeOutDurationForAudioAttributes(@onNull AudioAttributes audioAttributes)350     public long getFadeOutDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
351         ensureFadingIsEnabled();
352         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
353                 mAttrToFadeWrapperMap.get(audioAttributes), /* isFadeIn= */ false));
354     }
355 
356     /**
357      * Get the duration used to fade-in players with {@link android.media.AudioAttributes}
358      *
359      * @param audioAttributes {@link android.media.AudioAttributes}
360      * @return duration in milliseconds if set for the audio attributes or
361      *     {@link #DURATION_NOT_SET} otherwise
362      * @throws NullPointerException if the audio attributes is {@code null}
363      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
364      */
365     @IntRange(from = 0) @DurationMillisLong
getFadeInDurationForAudioAttributes(@onNull AudioAttributes audioAttributes)366     public long getFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
367         ensureFadingIsEnabled();
368         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
369                 mAttrToFadeWrapperMap.get(audioAttributes), /* isFadeIn= */ true));
370     }
371 
372     /**
373      * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
374      * {@link android.media.AudioAttributes}
375      *
376      * @param audioAttributes {@link android.media.AudioAttributes}
377      * @return {@link android.media.VolumeShaper.Configuration} if set for the audio attribute or
378      *     {@code null} otherwise
379      * @throws NullPointerException if the audio attributes is {@code null}
380      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
381      */
382     @Nullable
getFadeOutVolumeShaperConfigForAudioAttributes( @onNull AudioAttributes audioAttributes)383     public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(
384             @NonNull AudioAttributes audioAttributes) {
385         Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
386         ensureFadingIsEnabled();
387         return getVolumeShaperConfigFromWrapper(mAttrToFadeWrapperMap.get(audioAttributes),
388                 /* isFadeIn= */ false);
389     }
390 
391     /**
392      * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
393      * {@link android.media.AudioAttributes}
394      *
395      * @param audioAttributes {@link android.media.AudioAttributes}
396      * @return {@link android.media.VolumeShaper.Configuration} used for fading in if set for the
397      *     audio attribute or {@code null} otherwise
398      * @throws NullPointerException if the audio attributes is {@code null}
399      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
400      */
401     @Nullable
getFadeInVolumeShaperConfigForAudioAttributes( @onNull AudioAttributes audioAttributes)402     public VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(
403             @NonNull AudioAttributes audioAttributes) {
404         Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
405         ensureFadingIsEnabled();
406         return getVolumeShaperConfigFromWrapper(mAttrToFadeWrapperMap.get(audioAttributes),
407                 /* isFadeIn= */ true);
408     }
409 
410     /**
411      * Get the list of {@link android.media.AudioAttributes} for whome the volume shaper
412      * configurations are defined
413      *
414      * @return list of {@link android.media.AudioAttributes} with valid volume shaper configs or
415      *     empty list if none set.
416      */
417     @NonNull
getAudioAttributesWithVolumeShaperConfigs()418     public List<AudioAttributes> getAudioAttributesWithVolumeShaperConfigs() {
419         return getAudioAttributesInternal();
420     }
421 
422     /**
423      * Get the delay after which the offending players are faded back in
424      *
425      * Players are categorized as offending if they do not honor audio focus state changes. For
426      * example - when an app loses audio focus, it is expected that the app stops any active
427      * player in favor of the app(s) that gained audio focus. However, if the app do not stop the
428      * audio playback, such players are termed as offenders.
429      *
430      * @return delay in milliseconds
431      */
432     @IntRange(from = 0) @DurationMillisLong
getFadeInDelayForOffenders()433     public long getFadeInDelayForOffenders() {
434         return mFadeInDelayForOffendersMillis;
435     }
436 
437     /**
438      * Query if fade is enabled
439      *
440      * @return {@code true} if fading is enabled, {@code false} otherwise
441      */
isFadeEnabled()442     public boolean isFadeEnabled() {
443         return mFadeState != FADE_STATE_DISABLED;
444     }
445 
446     /**
447      * Query if the usage is fadeable
448      *
449      * @param usage the {@link android.media.AudioAttributes usage}
450      * @return {@code true} if usage is fadeable, {@code false}  when the fade state is set to
451      *     {@link #FADE_STATE_DISABLED} or if the usage is not fadeable.
452      */
isUsageFadeable(@udioAttributes.AttributeUsage int usage)453     public boolean isUsageFadeable(@AudioAttributes.AttributeUsage int usage) {
454         if (!isFadeEnabled()) {
455             return false;
456         }
457         return mFadeableUsages.contains(usage);
458     }
459 
460     /**
461      * Query if the content type is unfadeable
462      *
463      * @param contentType the {@link android.media.AudioAttributes content type}
464      * @return {@code true} if content type is unfadeable or if fade state is set to
465      *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
466      */
isContentTypeUnfadeable(@udioAttributes.AttributeContentType int contentType)467     public boolean isContentTypeUnfadeable(@AudioAttributes.AttributeContentType int contentType) {
468         if (!isFadeEnabled()) {
469             return true;
470         }
471         return mUnfadeableContentTypes.contains(contentType);
472     }
473 
474     /**
475      * Query if the player type is unfadeable
476      *
477      * @param playerType the {@link android.media.AudioPlaybackConfiguration player type}
478      * @return {@code true} if player type is unfadeable or if fade state is set to
479      *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
480      */
isPlayerTypeUnfadeable(@udioPlaybackConfiguration.PlayerType int playerType)481     public boolean isPlayerTypeUnfadeable(@AudioPlaybackConfiguration.PlayerType int playerType) {
482         if (!isFadeEnabled()) {
483             return true;
484         }
485         return mUnfadeablePlayerTypes.contains(playerType);
486     }
487 
488     /**
489      * Query if the audio attributes is unfadeable
490      *
491      * @param audioAttributes the {@link android.media.AudioAttributes}
492      * @return {@code true} if audio attributes is unfadeable or if fade state is set to
493      *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
494      * @throws NullPointerException if the audio attributes is {@code null}
495      */
isAudioAttributesUnfadeable(@onNull AudioAttributes audioAttributes)496     public boolean isAudioAttributesUnfadeable(@NonNull AudioAttributes audioAttributes) {
497         Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
498         if (!isFadeEnabled()) {
499             return true;
500         }
501         return mUnfadeableAudioAttributes.contains(audioAttributes);
502     }
503 
504     /**
505      * Query if the uid is unfadeable
506      *
507      * @param uid the uid of application
508      * @return {@code true} if uid is unfadeable or if fade state is set to
509      *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
510      */
isUidUnfadeable(int uid)511     public boolean isUidUnfadeable(int uid) {
512         if (!isFadeEnabled()) {
513             return true;
514         }
515         return mUnfadeableUids.contains(uid);
516     }
517 
518     /**
519      * Returns the default fade out duration (in milliseconds)
520      */
getDefaultFadeOutDurationMillis()521     public static @IntRange(from = 1) @DurationMillisLong long getDefaultFadeOutDurationMillis() {
522         return DEFAULT_FADE_OUT_DURATION_MS;
523     }
524 
525     /**
526      * Returns the default fade in duration (in milliseconds)
527      */
getDefaultFadeInDurationMillis()528     public static @IntRange(from = 1) @DurationMillisLong long getDefaultFadeInDurationMillis() {
529         return DEFAULT_FADE_IN_DURATION_MS;
530     }
531 
532     @Override
toString()533     public String toString() {
534         return "FadeManagerConfiguration { fade state = " + fadeStateToString(mFadeState)
535                 + ", fade out duration = " + mFadeOutDurationMillis
536                 + ", fade in duration = " + mFadeInDurationMillis
537                 + ", offenders fade in delay = " + mFadeInDelayForOffendersMillis
538                 + ", fade volume shapers for audio attributes = " + mAttrToFadeWrapperMap
539                 + ", fadeable usages = " + mFadeableUsages.toString()
540                 + ", unfadeable content types = " + mUnfadeableContentTypes.toString()
541                 + ", unfadeable player types = " + mUnfadeablePlayerTypes.toString()
542                 + ", unfadeable uids = " + mUnfadeableUids.toString()
543                 + ", unfadeable audio attributes = " + mUnfadeableAudioAttributes + "}";
544     }
545 
546     /**
547      * Convert fade state into a human-readable string
548      *
549      * @param fadeState one of {@link #FADE_STATE_DISABLED} or {@link #FADE_STATE_ENABLED_DEFAULT}
550      * @return human-readable string
551      * @hide
552      */
553     @NonNull
fadeStateToString(@adeStateEnum int fadeState)554     public static String fadeStateToString(@FadeStateEnum int fadeState) {
555         switch (fadeState) {
556             case FADE_STATE_DISABLED:
557                 return "FADE_STATE_DISABLED";
558             case FADE_STATE_ENABLED_DEFAULT:
559                 return "FADE_STATE_ENABLED_DEFAULT";
560             default:
561                 return "unknown fade state: " + fadeState;
562         }
563     }
564 
565     @Override
describeContents()566     public int describeContents() {
567         return 0;
568     }
569 
570     @Override
equals(Object o)571     public boolean equals(Object o) {
572         if (this == o) {
573             return true;
574         }
575 
576         if (!(o instanceof FadeManagerConfiguration)) {
577             return false;
578         }
579 
580         FadeManagerConfiguration rhs = (FadeManagerConfiguration) o;
581 
582         return mUsageToFadeWrapperMap.contentEquals(rhs.mUsageToFadeWrapperMap)
583                 && mAttrToFadeWrapperMap.equals(rhs.mAttrToFadeWrapperMap)
584                 && Arrays.equals(mFadeableUsages.toArray(), rhs.mFadeableUsages.toArray())
585                 && Arrays.equals(mUnfadeableContentTypes.toArray(),
586                 rhs.mUnfadeableContentTypes.toArray())
587                 && Arrays.equals(mUnfadeablePlayerTypes.toArray(),
588                 rhs.mUnfadeablePlayerTypes.toArray())
589                 && Arrays.equals(mUnfadeableUids.toArray(), rhs.mUnfadeableUids.toArray())
590                 && mUnfadeableAudioAttributes.equals(rhs.mUnfadeableAudioAttributes)
591                 && mFadeState == rhs.mFadeState
592                 && mFadeOutDurationMillis == rhs.mFadeOutDurationMillis
593                 && mFadeInDurationMillis == rhs.mFadeInDurationMillis
594                 && mFadeInDelayForOffendersMillis == rhs.mFadeInDelayForOffendersMillis;
595     }
596 
597     @Override
hashCode()598     public int hashCode() {
599         return Objects.hash(mUsageToFadeWrapperMap, mAttrToFadeWrapperMap, mFadeableUsages,
600                 mUnfadeableContentTypes, mUnfadeablePlayerTypes, mUnfadeableAudioAttributes,
601                 mUnfadeableUids, mFadeState, mFadeOutDurationMillis, mFadeInDurationMillis,
602                 mFadeInDelayForOffendersMillis);
603     }
604 
605     @Override
writeToParcel(@onNull Parcel dest, int flags)606     public void writeToParcel(@NonNull Parcel dest, int flags) {
607         dest.writeInt(mFadeState);
608         dest.writeLong(mFadeOutDurationMillis);
609         dest.writeLong(mFadeInDurationMillis);
610         dest.writeLong(mFadeInDelayForOffendersMillis);
611         dest.writeTypedSparseArray(mUsageToFadeWrapperMap, flags);
612         dest.writeMap(mAttrToFadeWrapperMap);
613         dest.writeIntArray(mFadeableUsages.toArray());
614         dest.writeIntArray(mUnfadeableContentTypes.toArray());
615         dest.writeIntArray(mUnfadeablePlayerTypes.toArray());
616         dest.writeIntArray(mUnfadeableUids.toArray());
617         dest.writeTypedList(mUnfadeableAudioAttributes, flags);
618     }
619 
620     /**
621      * Creates fade manage configuration from parcel
622      *
623      * @hide
624      */
625     @VisibleForTesting()
FadeManagerConfiguration(Parcel in)626     FadeManagerConfiguration(Parcel in) {
627         int fadeState = in.readInt();
628         long fadeOutDurationMillis = in.readLong();
629         long fadeInDurationMillis = in.readLong();
630         long fadeInDelayForOffenders = in.readLong();
631         SparseArray<FadeVolumeShaperConfigsWrapper> usageToWrapperMap =
632                 in.createTypedSparseArray(FadeVolumeShaperConfigsWrapper.CREATOR);
633         ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap =
634                 new ArrayMap<>();
635         in.readMap(attrToFadeWrapperMap, getClass().getClassLoader(), AudioAttributes.class,
636                 FadeVolumeShaperConfigsWrapper.class);
637         int[] fadeableUsages = in.createIntArray();
638         int[] unfadeableContentTypes = in.createIntArray();
639         int[] unfadeablePlayerTypes = in.createIntArray();
640         int[] unfadeableUids = in.createIntArray();
641         List<AudioAttributes> unfadeableAudioAttributes = new ArrayList<>();
642         in.readTypedList(unfadeableAudioAttributes, AudioAttributes.CREATOR);
643 
644         this.mFadeState = fadeState;
645         this.mFadeOutDurationMillis = fadeOutDurationMillis;
646         this.mFadeInDurationMillis = fadeInDurationMillis;
647         this.mFadeInDelayForOffendersMillis = fadeInDelayForOffenders;
648         this.mUsageToFadeWrapperMap = usageToWrapperMap;
649         this.mAttrToFadeWrapperMap = attrToFadeWrapperMap;
650         this.mFadeableUsages = IntArray.wrap(fadeableUsages);
651         this.mUnfadeableContentTypes = IntArray.wrap(unfadeableContentTypes);
652         this.mUnfadeablePlayerTypes = IntArray.wrap(unfadeablePlayerTypes);
653         this.mUnfadeableUids = IntArray.wrap(unfadeableUids);
654         this.mUnfadeableAudioAttributes = unfadeableAudioAttributes;
655     }
656 
657     @NonNull
658     public static final Creator<FadeManagerConfiguration> CREATOR = new Creator<>() {
659         @Override
660         @NonNull
661         public FadeManagerConfiguration createFromParcel(@NonNull Parcel in) {
662             return new FadeManagerConfiguration(in);
663         }
664 
665         @Override
666         @NonNull
667         public FadeManagerConfiguration[] newArray(int size) {
668             return new FadeManagerConfiguration[size];
669         }
670     };
671 
getDurationForVolumeShaperConfig(VolumeShaper.Configuration config)672     private long getDurationForVolumeShaperConfig(VolumeShaper.Configuration config) {
673         return config != null ? config.getDuration() : DURATION_NOT_SET;
674     }
675 
getVolumeShaperConfigFromWrapper( FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn)676     private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
677             FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
678         // if no volume shaper config is available, return null
679         if (wrapper == null) {
680             return null;
681         }
682         if (isFadeIn) {
683             return wrapper.getFadeInVolShaperConfig();
684         }
685         return wrapper.getFadeOutVolShaperConfig();
686     }
687 
getAudioAttributesInternal()688     private List<AudioAttributes> getAudioAttributesInternal() {
689         List<AudioAttributes> attrs = new ArrayList<>(mAttrToFadeWrapperMap.size());
690         for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
691             attrs.add(mAttrToFadeWrapperMap.keyAt(index));
692         }
693         return attrs;
694     }
695 
isUsageValid(int usage)696     private static boolean isUsageValid(int usage) {
697         return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage)
698                 || AudioAttributes.isHiddenUsage(usage);
699     }
700 
ensureFadingIsEnabled()701     private void ensureFadingIsEnabled() {
702         if (!isFadeEnabled()) {
703             throw new IllegalStateException("Method call not allowed when fade is disabled");
704         }
705     }
706 
validateUsage(int usage)707     private static void validateUsage(int usage) {
708         Preconditions.checkArgument(isUsageValid(usage), "Invalid usage: %s", usage);
709     }
710 
convertIntegerListToIntArray(List<Integer> integerList)711     private static IntArray convertIntegerListToIntArray(List<Integer> integerList) {
712         if (integerList == null) {
713             return new IntArray();
714         }
715 
716         IntArray intArray = new IntArray(integerList.size());
717         for (int index = 0; index < integerList.size(); index++) {
718             intArray.add(integerList.get(index));
719         }
720         return intArray;
721     }
722 
convertIntArrayToIntegerList(IntArray intArray)723     private static List<Integer> convertIntArrayToIntegerList(IntArray intArray) {
724         if (intArray == null) {
725             return new ArrayList<>();
726         }
727 
728         ArrayList<Integer> integerArrayList = new ArrayList<>(intArray.size());
729         for (int index = 0; index < intArray.size(); index++) {
730             integerArrayList.add(intArray.get(index));
731         }
732         return integerArrayList;
733     }
734 
735     /**
736      * Builder class for {@link FadeManagerConfiguration} objects.
737      *
738      * <p><b>Notes:</b>
739      * <ul>
740      *     <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT}, the builder expects at
741      *     least one valid usage to be set/added. Failure to do so will result in an exception
742      *     during {@link #build()}</li>
743      *     <li>Every usage added to the fadeable list should have corresponding volume shaper
744      *     configs defined. This can be achieved by setting either the duration or volume shaper
745      *     config through {@link #setFadeOutDurationForUsage(int, long)} or
746      *     {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
747      *     <li> It is recommended to set volume shaper configurations individually for fade out and
748      *     fade in</li>
749      *     <li>For any incomplete volume shaper configurations, a volume shaper configuration will
750      *     be created using either the default fade durations or the ones provided as part of the
751      *     {@link #Builder(long, long)}</li>
752      *     <li>Additional volume shaper configs can also configured for a given usage
753      *     with additional attributes like content-type in order to achieve finer fade controls.
754      *     See:
755      *     {@link #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
756      *     VolumeShaper.Configuration)} and
757      *     {@link #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
758      *     VolumeShaper.Configuration)} </li>
759      *     </ul>
760      *
761      */
762     @SuppressWarnings("WeakerAccess")
763     public static final class Builder {
764         private static final int INVALID_INDEX = -1;
765         private static final long IS_BUILDER_USED_FIELD_SET = 1 << 0;
766         private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1;
767         private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2;
768 
769         /**
770          * delay after which a faded out player will be faded back in. This will be heard by the
771          * user only in the case of unmuting players that didn't respect audio focus and didn't
772          * stop/pause when their app lost focus.
773          * This is the amount of time between the app being notified of the focus loss
774          * (when its muted by the fade out), and the time fade in (to unmute) starts
775          */
776         private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2_000;
777 
778 
779         private static final IntArray DEFAULT_UNFADEABLE_PLAYER_TYPES = IntArray.wrap(new int[]{
780                 AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
781                 AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
782         });
783 
784         private static final IntArray DEFAULT_UNFADEABLE_CONTENT_TYPES = IntArray.wrap(new int[]{
785                 AudioAttributes.CONTENT_TYPE_SPEECH
786         });
787 
788         private static final IntArray DEFAULT_FADEABLE_USAGES = IntArray.wrap(new int[]{
789                 AudioAttributes.USAGE_GAME,
790                 AudioAttributes.USAGE_MEDIA
791         });
792 
793         private int mFadeState = FADE_STATE_ENABLED_DEFAULT;
794         private @DurationMillisLong long mFadeInDelayForOffendersMillis =
795                 DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
796         private @DurationMillisLong long mFadeOutDurationMillis;
797         private @DurationMillisLong long mFadeInDurationMillis;
798         private long mBuilderFieldsSet;
799         private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap =
800                 new SparseArray<>();
801         private ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap =
802                 new ArrayMap<>();
803         private IntArray mFadeableUsages = new IntArray();
804         private IntArray mUnfadeableContentTypes = new IntArray();
805         // Player types are not yet configurable
806         private IntArray mUnfadeablePlayerTypes = DEFAULT_UNFADEABLE_PLAYER_TYPES;
807         private IntArray mUnfadeableUids = new IntArray();
808         private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>();
809 
810         /**
811          * Constructs a new Builder with {@link #DEFAULT_FADE_OUT_DURATION_MS} and
812          * {@link #DEFAULT_FADE_IN_DURATION_MS} durations.
813          */
Builder()814         public Builder() {
815             mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS;
816             mFadeInDurationMillis = DEFAULT_FADE_IN_DURATION_MS;
817         }
818 
819         /**
820          * Constructs a new Builder with the provided fade out and fade in durations
821          *
822          * @param fadeOutDurationMillis duration in milliseconds used for fading out
823          * @param fadeInDurationMills duration in milliseconds used for fading in
824          */
Builder(@ntRangefrom = 1) @urationMillisLong long fadeOutDurationMillis, @IntRange(from = 1) @DurationMillisLong long fadeInDurationMills)825         public Builder(@IntRange(from = 1) @DurationMillisLong long fadeOutDurationMillis,
826                 @IntRange(from = 1) @DurationMillisLong long fadeInDurationMills) {
827             mFadeOutDurationMillis = fadeOutDurationMillis;
828             mFadeInDurationMillis = fadeInDurationMills;
829         }
830 
831         /**
832          * Constructs a new Builder from the given {@link FadeManagerConfiguration}
833          *
834          * @param fmc the {@link FadeManagerConfiguration} object whose data will be reused in the
835          *            new builder
836          */
Builder(@onNull FadeManagerConfiguration fmc)837         public Builder(@NonNull FadeManagerConfiguration fmc) {
838             mFadeState = fmc.mFadeState;
839             copyUsageToFadeWrapperMapInternal(fmc.mUsageToFadeWrapperMap);
840             mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>(
841                     fmc.mAttrToFadeWrapperMap);
842             mFadeableUsages = fmc.mFadeableUsages.clone();
843             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
844             mUnfadeableContentTypes = fmc.mUnfadeableContentTypes.clone();
845             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
846             mUnfadeablePlayerTypes = fmc.mUnfadeablePlayerTypes.clone();
847             mUnfadeableUids = fmc.mUnfadeableUids.clone();
848             mUnfadeableAudioAttributes = new ArrayList<>(fmc.mUnfadeableAudioAttributes);
849             mFadeOutDurationMillis = fmc.mFadeOutDurationMillis;
850             mFadeInDurationMillis = fmc.mFadeInDurationMillis;
851         }
852 
853         /**
854          * Set the overall fade state
855          *
856          * @param state one of the {@link #FADE_STATE_DISABLED} or
857          *     {@link #FADE_STATE_ENABLED_DEFAULT} states
858          * @return the same Builder instance
859          * @throws IllegalArgumentException if the fade state is invalid
860          * @see #getFadeState()
861          */
862         @NonNull
setFadeState(@adeStateEnum int state)863         public Builder setFadeState(@FadeStateEnum int state) {
864             validateFadeState(state);
865             mFadeState = state;
866             return this;
867         }
868 
869         /**
870          * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
871          * {@link android.media.AudioAttributes usage}
872          * <p>
873          * This method accepts {@code null} for volume shaper config to clear a previously set
874          * configuration (example, if set through
875          * {@link #Builder(android.media.FadeManagerConfiguration)})
876          *
877          * @param usage the {@link android.media.AudioAttributes usage} of target player
878          * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
879          *     to fade out players with usage
880          * @return the same Builder instance
881          * @throws IllegalArgumentException if the usage is invalid
882          * @see #getFadeOutVolumeShaperConfigForUsage(int)
883          */
884         @NonNull
setFadeOutVolumeShaperConfigForUsage( @udioAttributes.AttributeUsage int usage, @Nullable VolumeShaper.Configuration fadeOutVShaperConfig)885         public Builder setFadeOutVolumeShaperConfigForUsage(
886                 @AudioAttributes.AttributeUsage int usage,
887                 @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
888             validateUsage(usage);
889             getFadeVolShaperConfigWrapperForUsage(usage)
890                     .setFadeOutVolShaperConfig(fadeOutVShaperConfig);
891             cleanupInactiveWrapperEntries(usage);
892             return this;
893         }
894 
895         /**
896          * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
897          * {@link android.media.AudioAttributes usage}
898          * <p>
899          * This method accepts {@code null} for volume shaper config to clear a previously set
900          * configuration (example, if set through
901          * {@link #Builder(android.media.FadeManagerConfiguration)})
902          *
903          * @param usage the {@link android.media.AudioAttributes usage}
904          * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
905          *     to fade in players with usage
906          * @return the same Builder instance
907          * @throws IllegalArgumentException if the usage is invalid
908          * @see #getFadeInVolumeShaperConfigForUsage(int)
909          */
910         @NonNull
setFadeInVolumeShaperConfigForUsage( @udioAttributes.AttributeUsage int usage, @Nullable VolumeShaper.Configuration fadeInVShaperConfig)911         public Builder setFadeInVolumeShaperConfigForUsage(
912                 @AudioAttributes.AttributeUsage int usage,
913                 @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
914             validateUsage(usage);
915             getFadeVolShaperConfigWrapperForUsage(usage)
916                     .setFadeInVolShaperConfig(fadeInVShaperConfig);
917             cleanupInactiveWrapperEntries(usage);
918             return this;
919         }
920 
921         /**
922          * Set the duration used for fading out players with
923          * {@link android.media.AudioAttributes usage}
924          * <p>
925          * A Volume shaper configuration is generated with the provided duration and default
926          * volume curve definitions. This config is then used to fade out players with given usage.
927          * <p>
928          * In order to clear previously set duration (example, if set through
929          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
930          * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
931          * {@code null}
932          *
933          * @param usage the {@link android.media.AudioAttributes usage} of target player
934          * @param fadeOutDurationMillis positive duration in milliseconds or
935          *     {@link #DURATION_NOT_SET}
936          * @return the same Builder instance
937          * @throws IllegalArgumentException if the fade out duration is non-positive with the
938          *     exception of {@link #DURATION_NOT_SET}
939          * @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
940          * @see #getFadeOutDurationForUsage(int)
941          */
942         @NonNull
setFadeOutDurationForUsage(@udioAttributes.AttributeUsage int usage, @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis)943         public Builder setFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage,
944                 @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis) {
945             validateUsage(usage);
946             VolumeShaper.Configuration fadeOutVShaperConfig =
947                     createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
948             setFadeOutVolumeShaperConfigForUsage(usage, fadeOutVShaperConfig);
949             return this;
950         }
951 
952         /**
953          * Set the duration used for fading in players with
954          * {@link android.media.AudioAttributes usage}
955          * <p>
956          * A Volume shaper configuration is generated with the provided duration and default
957          * volume curve definitions. This config is then used to fade in players with given usage.
958          * <p>
959          * <b>Note: </b>In order to clear previously set duration (example, if set through
960          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
961          * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
962          * {@code null}
963          *
964          * @param usage the {@link android.media.AudioAttributes usage} of target player
965          * @param fadeInDurationMillis positive duration in milliseconds or
966          *     {@link #DURATION_NOT_SET}
967          * @return the same Builder instance
968          * @throws IllegalArgumentException if the fade in duration is non-positive with the
969          *     exception of {@link #DURATION_NOT_SET}
970          * @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
971          * @see #getFadeInDurationForUsage(int)
972          */
973         @NonNull
setFadeInDurationForUsage(@udioAttributes.AttributeUsage int usage, @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis)974         public Builder setFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage,
975                 @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis) {
976             validateUsage(usage);
977             VolumeShaper.Configuration fadeInVShaperConfig =
978                     createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
979             setFadeInVolumeShaperConfigForUsage(usage, fadeInVShaperConfig);
980             return this;
981         }
982 
983         /**
984          * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
985          * {@link android.media.AudioAttributes}
986          * <p>
987          * This method accepts {@code null} for volume shaper config to clear a previously set
988          * configuration (example, set through
989          * {@link #Builder(android.media.FadeManagerConfiguration)})
990          *
991          * @param audioAttributes the {@link android.media.AudioAttributes}
992          * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
993          *     fade out players with audio attribute
994          * @return the same Builder instance
995          * @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes)
996          */
997         @NonNull
setFadeOutVolumeShaperConfigForAudioAttributes( @onNull AudioAttributes audioAttributes, @Nullable VolumeShaper.Configuration fadeOutVShaperConfig)998         public Builder setFadeOutVolumeShaperConfigForAudioAttributes(
999                 @NonNull AudioAttributes audioAttributes,
1000                 @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
1001             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1002             getFadeVolShaperConfigWrapperForAttr(audioAttributes)
1003                     .setFadeOutVolShaperConfig(fadeOutVShaperConfig);
1004             cleanupInactiveWrapperEntries(audioAttributes);
1005             return this;
1006         }
1007 
1008         /**
1009          * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
1010          * {@link android.media.AudioAttributes}
1011          *
1012          * <p>This method accepts {@code null} for volume shaper config to clear a previously set
1013          * configuration (example, set through
1014          * {@link #Builder(android.media.FadeManagerConfiguration)})
1015          *
1016          * @param audioAttributes the {@link android.media.AudioAttributes}
1017          * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
1018          *     fade in players with audio attribute
1019          * @return the same Builder instance
1020          * @throws NullPointerException if the audio attributes is {@code null}
1021          * @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes)
1022          */
1023         @NonNull
setFadeInVolumeShaperConfigForAudioAttributes( @onNull AudioAttributes audioAttributes, @Nullable VolumeShaper.Configuration fadeInVShaperConfig)1024         public Builder setFadeInVolumeShaperConfigForAudioAttributes(
1025                 @NonNull AudioAttributes audioAttributes,
1026                 @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
1027             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1028             getFadeVolShaperConfigWrapperForAttr(audioAttributes)
1029                     .setFadeInVolShaperConfig(fadeInVShaperConfig);
1030             cleanupInactiveWrapperEntries(audioAttributes);
1031             return this;
1032         }
1033 
1034         /**
1035          * Set the duration used for fading out players of type
1036          * {@link android.media.AudioAttributes}.
1037          * <p>
1038          * A Volume shaper configuration is generated with the provided duration and default
1039          * volume curve definitions. This config is then used to fade out players with given usage.
1040          * <p>
1041          * <b>Note: </b>In order to clear previously set duration (example, if set through
1042          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
1043          * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
1044          * {@code null}
1045          *
1046          * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out
1047          *     duration will be set/updated/reset
1048          * @param fadeOutDurationMillis positive duration in milliseconds or
1049          *     {@link #DURATION_NOT_SET}
1050          * @return the same Builder instance
1051          * @throws IllegalArgumentException if the fade out duration is non-positive with the
1052          *     exception of {@link #DURATION_NOT_SET}
1053          * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
1054          * @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
1055          * VolumeShaper.Configuration)
1056          */
1057         @NonNull
setFadeOutDurationForAudioAttributes( @onNull AudioAttributes audioAttributes, @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis)1058         public Builder setFadeOutDurationForAudioAttributes(
1059                 @NonNull AudioAttributes audioAttributes,
1060                 @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis) {
1061             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1062             VolumeShaper.Configuration fadeOutVShaperConfig =
1063                     createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
1064             setFadeOutVolumeShaperConfigForAudioAttributes(audioAttributes, fadeOutVShaperConfig);
1065             return this;
1066         }
1067 
1068         /**
1069          * Set the duration used for fading in players of type {@link android.media.AudioAttributes}
1070          * <p>
1071          * A Volume shaper configuration is generated with the provided duration and default
1072          * volume curve definitions. This config is then used to fade in players with given usage.
1073          * <p>
1074          * <b>Note: </b>In order to clear previously set duration (example, if set through
1075          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
1076          * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
1077          * {@code null}
1078          *
1079          * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in
1080          *     duration will be set/updated/reset
1081          * @param fadeInDurationMillis positive duration in milliseconds or
1082          *     {@link #DURATION_NOT_SET}
1083          * @return the same Builder instance
1084          * @throws IllegalArgumentException if the fade in duration is non-positive with the
1085          *     exception of {@link #DURATION_NOT_SET}
1086          * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
1087          * @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
1088          * VolumeShaper.Configuration)
1089          */
1090         @NonNull
setFadeInDurationForAudioAttributes(@onNull AudioAttributes audioAttributes, @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis)1091         public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes,
1092                 @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis) {
1093             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1094             VolumeShaper.Configuration fadeInVShaperConfig =
1095                     createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
1096             setFadeInVolumeShaperConfigForAudioAttributes(audioAttributes, fadeInVShaperConfig);
1097             return this;
1098         }
1099 
1100         /**
1101          * Set the list of {@link android.media.AudioAttributes usage} that can be faded
1102          *
1103          * <p>This is a positive list. Players with matching usage will be considered for fading.
1104          * Usages that are not part of this list will not be faded
1105          *
1106          * <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one
1107          * usage to be set/added. Failure to do so will result in an exception during
1108          * {@link #build()}
1109          *
1110          * @param usages List of the {@link android.media.AudioAttributes usages}
1111          * @return the same Builder instance
1112          * @throws IllegalArgumentException if the usages are invalid
1113          * @see #getFadeableUsages()
1114          */
1115         @NonNull
setFadeableUsages(@onNull List<Integer> usages)1116         public Builder setFadeableUsages(@NonNull List<Integer> usages) {
1117             Objects.requireNonNull(usages, "List of usages cannot be null");
1118             validateUsages(usages);
1119             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
1120             mFadeableUsages.clear();
1121             mFadeableUsages.addAll(convertIntegerListToIntArray(usages));
1122             return this;
1123         }
1124 
1125         /**
1126          * Add the {@link android.media.AudioAttributes usage} to the fadeable list
1127          *
1128          * @param usage the {@link android.media.AudioAttributes usage}
1129          * @return the same Builder instance
1130          * @throws IllegalArgumentException if the usage is invalid
1131          * @see #getFadeableUsages()
1132          * @see #setFadeableUsages(List)
1133          */
1134         @NonNull
addFadeableUsage(@udioAttributes.AttributeUsage int usage)1135         public Builder addFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
1136             validateUsage(usage);
1137             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
1138             if (!mFadeableUsages.contains(usage)) {
1139                 mFadeableUsages.add(usage);
1140             }
1141             return this;
1142         }
1143 
1144         /**
1145          * Clears the fadeable {@link android.media.AudioAttributes usage} list
1146          *
1147          * <p>This can be used to reset the list when using a copy constructor
1148          *
1149          * @return the same Builder instance
1150          * @see #getFadeableUsages()
1151          * @see #setFadeableUsages(List)
1152          */
1153         @NonNull
clearFadeableUsages()1154         public Builder clearFadeableUsages() {
1155             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
1156             mFadeableUsages.clear();
1157             return this;
1158         }
1159 
1160         /**
1161          * Set the list of {@link android.media.AudioAttributes content type} that can not be faded
1162          *
1163          * <p>This is a negative list. Players with matching content type of this list will not be
1164          * faded. Content types that are not part of this list will be considered for fading.
1165          *
1166          * <p>Passing an empty list as input clears the existing list. This can be used to
1167          * reset the list when using a copy constructor
1168          *
1169          * @param contentTypes list of {@link android.media.AudioAttributes content types}
1170          * @return the same Builder instance
1171          * @throws IllegalArgumentException if the content types are invalid
1172          * @see #getUnfadeableContentTypes()
1173          */
1174         @NonNull
setUnfadeableContentTypes(@onNull List<Integer> contentTypes)1175         public Builder setUnfadeableContentTypes(@NonNull List<Integer> contentTypes) {
1176             Objects.requireNonNull(contentTypes, "List of content types cannot be null");
1177             validateContentTypes(contentTypes);
1178             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
1179             mUnfadeableContentTypes.clear();
1180             mUnfadeableContentTypes.addAll(convertIntegerListToIntArray(contentTypes));
1181             return this;
1182         }
1183 
1184         /**
1185          * Add the {@link android.media.AudioAttributes content type} to unfadeable list
1186          *
1187          * @param contentType the {@link android.media.AudioAttributes content type}
1188          * @return the same Builder instance
1189          * @throws IllegalArgumentException if the content type is invalid
1190          * @see #setUnfadeableContentTypes(List)
1191          * @see #getUnfadeableContentTypes()
1192          */
1193         @NonNull
addUnfadeableContentType( @udioAttributes.AttributeContentType int contentType)1194         public Builder addUnfadeableContentType(
1195                 @AudioAttributes.AttributeContentType int contentType) {
1196             validateContentType(contentType);
1197             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
1198             if (!mUnfadeableContentTypes.contains(contentType)) {
1199                 mUnfadeableContentTypes.add(contentType);
1200             }
1201             return this;
1202         }
1203 
1204         /**
1205          * Clears the unfadeable {@link android.media.AudioAttributes content type} list
1206          *
1207          * <p>This can be used to reset the list when using a copy constructor
1208          *
1209          * @return the same Builder instance
1210          * @see #setUnfadeableContentTypes(List)
1211          * @see #getUnfadeableContentTypes()
1212          */
1213         @NonNull
clearUnfadeableContentTypes()1214         public Builder clearUnfadeableContentTypes() {
1215             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
1216             mUnfadeableContentTypes.clear();
1217             return this;
1218         }
1219 
1220         /**
1221          * Set the uids that cannot be faded
1222          *
1223          * <p>This is a negative list. Players with matching uid of this list will not be faded.
1224          * Uids that are not part of this list shall be considered for fading.
1225          *
1226          * @param uids list of uids
1227          * @return the same Builder instance
1228          * @see #getUnfadeableUids()
1229          */
1230         @NonNull
setUnfadeableUids(@onNull List<Integer> uids)1231         public Builder setUnfadeableUids(@NonNull List<Integer> uids) {
1232             Objects.requireNonNull(uids, "List of uids cannot be null");
1233             mUnfadeableUids.clear();
1234             mUnfadeableUids.addAll(convertIntegerListToIntArray(uids));
1235             return this;
1236         }
1237 
1238         /**
1239          * Add uid to unfadeable list
1240          *
1241          * @param uid client uid
1242          * @return the same Builder instance
1243          * @see #setUnfadeableUids(List)
1244          * @see #getUnfadeableUids()
1245          */
1246         @NonNull
addUnfadeableUid(int uid)1247         public Builder addUnfadeableUid(int uid) {
1248             if (!mUnfadeableUids.contains(uid)) {
1249                 mUnfadeableUids.add(uid);
1250             }
1251             return this;
1252         }
1253 
1254         /**
1255          * Clears the unfadeable uid list
1256          *
1257          * <p>This can be used to reset the list when using a copy constructor.
1258          *
1259          * @return the same Builder instance
1260          * @see #setUnfadeableUids(List)
1261          * @see #getUnfadeableUids()
1262          */
1263         @NonNull
clearUnfadeableUids()1264         public Builder clearUnfadeableUids() {
1265             mUnfadeableUids.clear();
1266             return this;
1267         }
1268 
1269         /**
1270          * Set the list of {@link android.media.AudioAttributes} that can not be faded
1271          *
1272          * <p>This is a negative list. Players with matching audio attributes of this list will not
1273          * be faded. Audio attributes that are not part of this list shall be considered for fading.
1274          *
1275          * <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can
1276          * negatively impact fadeability decision (if such an audio attribute and corresponding
1277          * usage fall into opposing lists).
1278          * For example:
1279          * <pre class=prettyprint>
1280          *    AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre>
1281          * is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}.
1282          * It is an undefined behavior to have an {@link android.media.AudioAttributes usage} in the
1283          * fadeable usage list and the corresponding generic {@link android.media.AudioAttributes}
1284          * in the unfadeable list. Such cases will result in an exception during {@link #build()}.
1285          *
1286          * @param attrs list of {@link android.media.AudioAttributes}
1287          * @return the same Builder instance
1288          * @see #getUnfadeableAudioAttributes()
1289          */
1290         @NonNull
setUnfadeableAudioAttributes(@onNull List<AudioAttributes> attrs)1291         public Builder setUnfadeableAudioAttributes(@NonNull List<AudioAttributes> attrs) {
1292             Objects.requireNonNull(attrs, "List of audio attributes cannot be null");
1293             mUnfadeableAudioAttributes.clear();
1294             mUnfadeableAudioAttributes.addAll(attrs);
1295             return this;
1296         }
1297 
1298         /**
1299          * Add the {@link android.media.AudioAttributes} to the unfadeable list
1300          *
1301          * @param audioAttributes the {@link android.media.AudioAttributes}
1302          * @return the same Builder instance
1303          * @see #setUnfadeableAudioAttributes(List)
1304          * @see #getUnfadeableAudioAttributes()
1305          */
1306         @NonNull
addUnfadeableAudioAttributes(@onNull AudioAttributes audioAttributes)1307         public Builder addUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
1308             Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
1309             if (!mUnfadeableAudioAttributes.contains(audioAttributes)) {
1310                 mUnfadeableAudioAttributes.add(audioAttributes);
1311             }
1312             return this;
1313         }
1314 
1315         /**
1316          * Clears the unfadeable {@link android.media.AudioAttributes} list.
1317          *
1318          * <p>This can be used to reset the list when using a copy constructor.
1319          *
1320          * @return the same Builder instance
1321          * @see #getUnfadeableAudioAttributes()
1322          */
1323         @NonNull
clearUnfadeableAudioAttributes()1324         public Builder clearUnfadeableAudioAttributes() {
1325             mUnfadeableAudioAttributes.clear();
1326             return this;
1327         }
1328 
1329         /**
1330          * Set the delay after which the offending faded out player will be faded in.
1331          *
1332          * <p>This is the amount of time between the app being notified of the focus loss (when its
1333          * muted by the fade out), and the time fade in (to unmute) starts
1334          *
1335          * @param delayMillis delay in milliseconds
1336          * @return the same Builder instance
1337          * @throws IllegalArgumentException if the delay is negative
1338          * @see #getFadeInDelayForOffenders()
1339          */
1340         @NonNull
setFadeInDelayForOffenders( @ntRangefrom = 0) @urationMillisLong long delayMillis)1341         public Builder setFadeInDelayForOffenders(
1342                 @IntRange(from = 0) @DurationMillisLong long delayMillis) {
1343             Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative");
1344             mFadeInDelayForOffendersMillis = delayMillis;
1345             return this;
1346         }
1347 
1348         /**
1349          * Builds the {@link FadeManagerConfiguration} with all of the fade configurations that
1350          * have been set.
1351          *
1352          * @return a new {@link FadeManagerConfiguration} object
1353          */
1354         @NonNull
build()1355         public FadeManagerConfiguration build() {
1356             if (!checkNotSet(IS_BUILDER_USED_FIELD_SET)) {
1357                 throw new IllegalStateException(
1358                         "This Builder should not be reused. Use a new Builder instance instead");
1359             }
1360 
1361             setFlag(IS_BUILDER_USED_FIELD_SET);
1362 
1363             if (checkNotSet(IS_FADEABLE_USAGES_FIELD_SET)) {
1364                 mFadeableUsages = DEFAULT_FADEABLE_USAGES;
1365                 setVolShaperConfigsForUsages(mFadeableUsages);
1366             }
1367 
1368             if (checkNotSet(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET)) {
1369                 mUnfadeableContentTypes = DEFAULT_UNFADEABLE_CONTENT_TYPES;
1370             }
1371 
1372             validateFadeConfigurations();
1373 
1374             return new FadeManagerConfiguration(mFadeState, mFadeOutDurationMillis,
1375                     mFadeInDurationMillis, mFadeInDelayForOffendersMillis, mUsageToFadeWrapperMap,
1376                     mAttrToFadeWrapperMap, mFadeableUsages, mUnfadeableContentTypes,
1377                     mUnfadeablePlayerTypes, mUnfadeableUids, mUnfadeableAudioAttributes);
1378         }
1379 
setFlag(long flag)1380         private void setFlag(long flag) {
1381             mBuilderFieldsSet |= flag;
1382         }
1383 
checkNotSet(long flag)1384         private boolean checkNotSet(long flag) {
1385             return (mBuilderFieldsSet & flag) == 0;
1386         }
1387 
getFadeVolShaperConfigWrapperForUsage(int usage)1388         private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForUsage(int usage) {
1389             if (!mUsageToFadeWrapperMap.contains(usage)) {
1390                 mUsageToFadeWrapperMap.put(usage, new FadeVolumeShaperConfigsWrapper());
1391             }
1392             return mUsageToFadeWrapperMap.get(usage);
1393         }
1394 
getFadeVolShaperConfigWrapperForAttr( AudioAttributes attr)1395         private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForAttr(
1396                 AudioAttributes attr) {
1397             // if no entry, create a new one for setting/clearing
1398             if (!mAttrToFadeWrapperMap.containsKey(attr)) {
1399                 mAttrToFadeWrapperMap.put(attr, new FadeVolumeShaperConfigsWrapper());
1400             }
1401             return mAttrToFadeWrapperMap.get(attr);
1402         }
1403 
createVolShaperConfigForDuration(long duration, boolean isFadeIn)1404         private VolumeShaper.Configuration createVolShaperConfigForDuration(long duration,
1405                 boolean isFadeIn) {
1406             // used to reset the volume shaper config setting
1407             if (duration == DURATION_NOT_SET) {
1408                 return null;
1409             }
1410 
1411             VolumeShaper.Configuration.Builder builder = new VolumeShaper.Configuration.Builder()
1412                     .setId(VOLUME_SHAPER_SYSTEM_FADE_ID)
1413                     .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
1414                     .setDuration(duration);
1415 
1416             if (isFadeIn) {
1417                 builder.setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
1418                         /* volumes= */ new float[]{0.f, 0.30f, 1.0f});
1419             } else {
1420                 builder.setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
1421                         /* volumes= */ new float[]{1.f, 0.65f, 0.0f});
1422             }
1423 
1424             return builder.build();
1425         }
1426 
cleanupInactiveWrapperEntries(int usage)1427         private void cleanupInactiveWrapperEntries(int usage) {
1428             FadeVolumeShaperConfigsWrapper fmcw = mUsageToFadeWrapperMap.get(usage);
1429             // cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
1430             if (fmcw != null && fmcw.isInactive()) {
1431                 mUsageToFadeWrapperMap.remove(usage);
1432             }
1433         }
1434 
cleanupInactiveWrapperEntries(AudioAttributes attr)1435         private void cleanupInactiveWrapperEntries(AudioAttributes attr) {
1436             FadeVolumeShaperConfigsWrapper fmcw = mAttrToFadeWrapperMap.get(attr);
1437             // cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
1438             if (fmcw != null && fmcw.isInactive()) {
1439                 mAttrToFadeWrapperMap.remove(attr);
1440             }
1441         }
1442 
setVolShaperConfigsForUsages(IntArray usages)1443         private void setVolShaperConfigsForUsages(IntArray usages) {
1444             // set default volume shaper configs for fadeable usages
1445             for (int index = 0; index < usages.size(); index++) {
1446                 setMissingVolShaperConfigsForWrapper(
1447                         getFadeVolShaperConfigWrapperForUsage(usages.get(index)));
1448             }
1449         }
1450 
setMissingVolShaperConfigsForWrapper(FadeVolumeShaperConfigsWrapper wrapper)1451         private void setMissingVolShaperConfigsForWrapper(FadeVolumeShaperConfigsWrapper wrapper) {
1452             if (!wrapper.isFadeOutConfigActive()) {
1453                 wrapper.setFadeOutVolShaperConfig(createVolShaperConfigForDuration(
1454                         mFadeOutDurationMillis, /* isFadeIn= */ false));
1455             }
1456             if (!wrapper.isFadeInConfigActive()) {
1457                 wrapper.setFadeInVolShaperConfig(createVolShaperConfigForDuration(
1458                         mFadeInDurationMillis, /* isFadeIn= */ true));
1459             }
1460         }
1461 
copyUsageToFadeWrapperMapInternal( SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap)1462         private  void copyUsageToFadeWrapperMapInternal(
1463                 SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap) {
1464             for (int index = 0; index < usageToFadeWrapperMap.size(); index++) {
1465                 mUsageToFadeWrapperMap.put(usageToFadeWrapperMap.keyAt(index),
1466                         new FadeVolumeShaperConfigsWrapper(usageToFadeWrapperMap.valueAt(index)));
1467             }
1468         }
1469 
validateFadeState(int state)1470         private void validateFadeState(int state) {
1471             switch(state) {
1472                 case FADE_STATE_DISABLED:
1473                 case FADE_STATE_ENABLED_DEFAULT:
1474                     break;
1475                 default:
1476                     throw new IllegalArgumentException("Unknown fade state: " + state);
1477             }
1478         }
1479 
validateUsages(List<Integer> usages)1480         private void validateUsages(List<Integer> usages) {
1481             for (int index = 0; index < usages.size(); index++) {
1482                 validateUsage(usages.get(index));
1483             }
1484         }
1485 
validateContentTypes(List<Integer> contentTypes)1486         private void validateContentTypes(List<Integer> contentTypes) {
1487             for (int index = 0; index < contentTypes.size(); index++) {
1488                 validateContentType(contentTypes.get(index));
1489             }
1490         }
1491 
validateContentType(int contentType)1492         private void validateContentType(int contentType) {
1493             Preconditions.checkArgument(AudioAttributes.isSdkContentType(contentType),
1494                     "Invalid content type: ", contentType);
1495         }
1496 
validateFadeConfigurations()1497         private void validateFadeConfigurations() {
1498             validateFadeableUsages();
1499             validateFadeVolumeShaperConfigsWrappers();
1500             validateUnfadeableAudioAttributes();
1501         }
1502 
1503         /** Ensure fadeable usage list meets config requirements */
validateFadeableUsages()1504         private void validateFadeableUsages() {
1505             // ensure at least one fadeable usage
1506             Preconditions.checkArgumentPositive(mFadeableUsages.size(),
1507                     "Fadeable usage list cannot be empty when state set to enabled");
1508             // ensure all fadeable usages have volume shaper configs - both fade in and out
1509             for (int index = 0; index < mFadeableUsages.size(); index++) {
1510                 setMissingVolShaperConfigsForWrapper(
1511                         getFadeVolShaperConfigWrapperForUsage(mFadeableUsages.get(index)));
1512             }
1513         }
1514 
1515         /** Ensure Fade volume shaper config wrappers meet requirements */
validateFadeVolumeShaperConfigsWrappers()1516         private void validateFadeVolumeShaperConfigsWrappers() {
1517             // ensure both fade in & out volume shaper configs are defined for all wrappers
1518             // for usages -
1519             for (int index = 0; index < mUsageToFadeWrapperMap.size(); index++) {
1520                 setMissingVolShaperConfigsForWrapper(
1521                         getFadeVolShaperConfigWrapperForUsage(mUsageToFadeWrapperMap.keyAt(index)));
1522             }
1523 
1524             // for additional audio attributes -
1525             for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
1526                 setMissingVolShaperConfigsForWrapper(
1527                         getFadeVolShaperConfigWrapperForAttr(mAttrToFadeWrapperMap.keyAt(index)));
1528             }
1529         }
1530 
1531         /** Ensure Unfadeable attributes meet configuration requirements */
validateUnfadeableAudioAttributes()1532         private void validateUnfadeableAudioAttributes() {
1533             // ensure no generic AudioAttributes in unfadeable list with matching usage in fadeable
1534             // list. failure results in an undefined behavior as the audio attributes
1535             // shall be both fadeable (because of the usage) and unfadeable at the same time.
1536             for (int index = 0; index < mUnfadeableAudioAttributes.size(); index++) {
1537                 AudioAttributes targetAttr = mUnfadeableAudioAttributes.get(index);
1538                 int usage = targetAttr.getSystemUsage();
1539                 boolean isFadeableUsage = mFadeableUsages.contains(usage);
1540                 // cannot have a generic audio attribute that also is a fadeable usage
1541                 Preconditions.checkArgument(
1542                         !isFadeableUsage || (isFadeableUsage && !isGeneric(targetAttr)),
1543                         "Unfadeable audio attributes cannot be generic of the fadeable usage");
1544             }
1545         }
1546 
isGeneric(AudioAttributes attr)1547         private static boolean isGeneric(AudioAttributes attr) {
1548             return (attr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN
1549                     && attr.getFlags() == 0x0
1550                     && attr.getBundle() == null
1551                     && attr.getTags().isEmpty());
1552         }
1553     }
1554 
1555     private static final class FadeVolumeShaperConfigsWrapper implements Parcelable {
1556         // null volume shaper config refers to either init state or if its cleared/reset
1557         private @Nullable VolumeShaper.Configuration mFadeOutVolShaperConfig;
1558         private @Nullable VolumeShaper.Configuration mFadeInVolShaperConfig;
1559 
FadeVolumeShaperConfigsWrapper()1560         FadeVolumeShaperConfigsWrapper() {}
1561 
FadeVolumeShaperConfigsWrapper(@onNull FadeVolumeShaperConfigsWrapper wrapper)1562         FadeVolumeShaperConfigsWrapper(@NonNull FadeVolumeShaperConfigsWrapper wrapper) {
1563             Objects.requireNonNull(wrapper, "Fade volume shaper configs wrapper cannot be null");
1564             this.mFadeOutVolShaperConfig = wrapper.mFadeOutVolShaperConfig;
1565             this.mFadeInVolShaperConfig = wrapper.mFadeInVolShaperConfig;
1566         }
1567 
setFadeOutVolShaperConfig(@ullable VolumeShaper.Configuration fadeOutConfig)1568         public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) {
1569             mFadeOutVolShaperConfig = fadeOutConfig;
1570         }
1571 
setFadeInVolShaperConfig(@ullable VolumeShaper.Configuration fadeInConfig)1572         public void setFadeInVolShaperConfig(@Nullable VolumeShaper.Configuration fadeInConfig) {
1573             mFadeInVolShaperConfig = fadeInConfig;
1574         }
1575 
1576         /**
1577          * Query fade out volume shaper config
1578          *
1579          * @return configured fade out volume shaper config or {@code null} when initialized/reset
1580          */
1581         @Nullable
getFadeOutVolShaperConfig()1582         public VolumeShaper.Configuration getFadeOutVolShaperConfig() {
1583             return mFadeOutVolShaperConfig;
1584         }
1585 
1586         /**
1587          * Query fade in volume shaper config
1588          *
1589          * @return configured fade in volume shaper config or {@code null} when initialized/reset
1590          */
1591         @Nullable
getFadeInVolShaperConfig()1592         public VolumeShaper.Configuration getFadeInVolShaperConfig() {
1593             return mFadeInVolShaperConfig;
1594         }
1595 
1596         /**
1597          * Wrapper is inactive if both fade out and in configs are cleared.
1598          *
1599          * @return {@code true} if configs are cleared. {@code false} if either of the configs is
1600          * set
1601          */
isInactive()1602         public boolean isInactive() {
1603             return !isFadeOutConfigActive() && !isFadeInConfigActive();
1604         }
1605 
isFadeOutConfigActive()1606         boolean isFadeOutConfigActive() {
1607             return mFadeOutVolShaperConfig != null;
1608         }
1609 
isFadeInConfigActive()1610         boolean isFadeInConfigActive() {
1611             return mFadeInVolShaperConfig != null;
1612         }
1613 
1614         @Override
equals(Object o)1615         public boolean equals(Object o) {
1616             if (this == o) {
1617                 return true;
1618             }
1619 
1620             if (!(o instanceof FadeVolumeShaperConfigsWrapper)) {
1621                 return false;
1622             }
1623 
1624             FadeVolumeShaperConfigsWrapper rhs = (FadeVolumeShaperConfigsWrapper) o;
1625 
1626             if (mFadeInVolShaperConfig == null && rhs.mFadeInVolShaperConfig == null
1627                     && mFadeOutVolShaperConfig == null && rhs.mFadeOutVolShaperConfig == null) {
1628                 return true;
1629             }
1630 
1631             boolean isEqual;
1632             if (mFadeOutVolShaperConfig != null) {
1633                 isEqual = mFadeOutVolShaperConfig.equals(rhs.mFadeOutVolShaperConfig);
1634             } else if (rhs.mFadeOutVolShaperConfig != null) {
1635                 return false;
1636             } else {
1637                 isEqual = true;
1638             }
1639 
1640             if (mFadeInVolShaperConfig != null) {
1641                 isEqual = isEqual && mFadeInVolShaperConfig.equals(rhs.mFadeInVolShaperConfig);
1642             } else if (rhs.mFadeInVolShaperConfig != null) {
1643                 return false;
1644             }
1645 
1646             return isEqual;
1647         }
1648 
1649         @Override
hashCode()1650         public int hashCode() {
1651             return Objects.hash(mFadeOutVolShaperConfig, mFadeInVolShaperConfig);
1652         }
1653 
1654         @Override
describeContents()1655         public int describeContents() {
1656             return 0;
1657         }
1658 
1659         @Override
writeToParcel(@onNull Parcel dest, int flags)1660         public void writeToParcel(@NonNull Parcel dest, int flags) {
1661             mFadeOutVolShaperConfig.writeToParcel(dest, flags);
1662             mFadeInVolShaperConfig.writeToParcel(dest, flags);
1663         }
1664 
1665         /**
1666          * Creates fade volume shaper config wrapper from parcel
1667          *
1668          * @hide
1669          */
1670         @VisibleForTesting()
FadeVolumeShaperConfigsWrapper(Parcel in)1671         FadeVolumeShaperConfigsWrapper(Parcel in) {
1672             mFadeOutVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
1673             mFadeInVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
1674         }
1675 
1676         @NonNull
1677         public static final Creator<FadeVolumeShaperConfigsWrapper> CREATOR = new Creator<>() {
1678             @Override
1679             @NonNull
1680             public FadeVolumeShaperConfigsWrapper createFromParcel(@NonNull Parcel in) {
1681                 return new FadeVolumeShaperConfigsWrapper(in);
1682             }
1683 
1684             @Override
1685             @NonNull
1686             public FadeVolumeShaperConfigsWrapper[] newArray(int size) {
1687                 return new FadeVolumeShaperConfigsWrapper[size];
1688             }
1689         };
1690     }
1691 }
1692 
1693