• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
676     @Nullable
getVolumeShaperConfigFromWrapper( FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn)677     private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
678             FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
679         // if no volume shaper config is available, return null
680         if (wrapper == null) {
681             return null;
682         }
683         if (isFadeIn) {
684             return wrapper.getFadeInVolShaperConfig();
685         }
686         return wrapper.getFadeOutVolShaperConfig();
687     }
688 
getAudioAttributesInternal()689     private List<AudioAttributes> getAudioAttributesInternal() {
690         List<AudioAttributes> attrs = new ArrayList<>(mAttrToFadeWrapperMap.size());
691         for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
692             attrs.add(mAttrToFadeWrapperMap.keyAt(index));
693         }
694         return attrs;
695     }
696 
isUsageValid(int usage)697     private static boolean isUsageValid(int usage) {
698         return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage)
699                 || AudioAttributes.isHiddenUsage(usage);
700     }
701 
ensureFadingIsEnabled()702     private void ensureFadingIsEnabled() {
703         if (!isFadeEnabled()) {
704             throw new IllegalStateException("Method call not allowed when fade is disabled");
705         }
706     }
707 
validateUsage(int usage)708     private static void validateUsage(int usage) {
709         Preconditions.checkArgument(isUsageValid(usage), "Invalid usage: %s", usage);
710     }
711 
convertIntegerListToIntArray(List<Integer> integerList)712     private static IntArray convertIntegerListToIntArray(List<Integer> integerList) {
713         if (integerList == null) {
714             return new IntArray();
715         }
716 
717         IntArray intArray = new IntArray(integerList.size());
718         for (int index = 0; index < integerList.size(); index++) {
719             intArray.add(integerList.get(index));
720         }
721         return intArray;
722     }
723 
convertIntArrayToIntegerList(IntArray intArray)724     private static List<Integer> convertIntArrayToIntegerList(IntArray intArray) {
725         if (intArray == null) {
726             return new ArrayList<>();
727         }
728 
729         ArrayList<Integer> integerArrayList = new ArrayList<>(intArray.size());
730         for (int index = 0; index < intArray.size(); index++) {
731             integerArrayList.add(intArray.get(index));
732         }
733         return integerArrayList;
734     }
735 
736     /**
737      * Builder class for {@link FadeManagerConfiguration} objects.
738      *
739      * <p><b>Notes:</b>
740      * <ul>
741      *     <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT}, the builder expects at
742      *     least one valid usage to be set/added. Failure to do so will result in an exception
743      *     during {@link #build()}</li>
744      *     <li>Every usage added to the fadeable list should have corresponding volume shaper
745      *     configs defined. This can be achieved by setting either the duration or volume shaper
746      *     config through {@link #setFadeOutDurationForUsage(int, long)} or
747      *     {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
748      *     <li> It is recommended to set volume shaper configurations individually for fade out and
749      *     fade in</li>
750      *     <li>For any incomplete volume shaper configurations, a volume shaper configuration will
751      *     be created using either the default fade durations or the ones provided as part of the
752      *     {@link #Builder(long, long)}</li>
753      *     <li>Additional volume shaper configs can also configured for a given usage
754      *     with additional attributes like content-type in order to achieve finer fade controls.
755      *     See:
756      *     {@link #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
757      *     VolumeShaper.Configuration)} and
758      *     {@link #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
759      *     VolumeShaper.Configuration)} </li>
760      *     </ul>
761      *
762      */
763     @SuppressWarnings("WeakerAccess")
764     public static final class Builder {
765         private static final int INVALID_INDEX = -1;
766         private static final long IS_BUILDER_USED_FIELD_SET = 1 << 0;
767         private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1;
768         private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2;
769 
770         /**
771          * delay after which a faded out player will be faded back in. This will be heard by the
772          * user only in the case of unmuting players that didn't respect audio focus and didn't
773          * stop/pause when their app lost focus.
774          * This is the amount of time between the app being notified of the focus loss
775          * (when its muted by the fade out), and the time fade in (to unmute) starts
776          */
777         private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2_000;
778 
779 
780         private static final IntArray DEFAULT_UNFADEABLE_PLAYER_TYPES = IntArray.wrap(new int[]{
781                 AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
782                 AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
783         });
784 
785         private static final IntArray DEFAULT_UNFADEABLE_CONTENT_TYPES = IntArray.wrap(new int[]{
786                 AudioAttributes.CONTENT_TYPE_SPEECH
787         });
788 
789         private static final IntArray DEFAULT_FADEABLE_USAGES = IntArray.wrap(new int[]{
790                 AudioAttributes.USAGE_GAME,
791                 AudioAttributes.USAGE_MEDIA
792         });
793 
794         private int mFadeState = FADE_STATE_ENABLED_DEFAULT;
795         private @DurationMillisLong long mFadeInDelayForOffendersMillis =
796                 DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
797         private @DurationMillisLong long mFadeOutDurationMillis;
798         private @DurationMillisLong long mFadeInDurationMillis;
799         private long mBuilderFieldsSet;
800         private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap =
801                 new SparseArray<>();
802         private ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap =
803                 new ArrayMap<>();
804         private IntArray mFadeableUsages = new IntArray();
805         private IntArray mUnfadeableContentTypes = new IntArray();
806         // Player types are not yet configurable
807         private IntArray mUnfadeablePlayerTypes = DEFAULT_UNFADEABLE_PLAYER_TYPES;
808         private IntArray mUnfadeableUids = new IntArray();
809         private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>();
810 
811         /**
812          * Constructs a new Builder with {@link #DEFAULT_FADE_OUT_DURATION_MS} and
813          * {@link #DEFAULT_FADE_IN_DURATION_MS} durations.
814          */
Builder()815         public Builder() {
816             mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS;
817             mFadeInDurationMillis = DEFAULT_FADE_IN_DURATION_MS;
818         }
819 
820         /**
821          * Constructs a new Builder with the provided fade out and fade in durations
822          *
823          * @param fadeOutDurationMillis duration in milliseconds used for fading out
824          * @param fadeInDurationMills duration in milliseconds used for fading in
825          */
Builder(@ntRangefrom = 1) @urationMillisLong long fadeOutDurationMillis, @IntRange(from = 1) @DurationMillisLong long fadeInDurationMills)826         public Builder(@IntRange(from = 1) @DurationMillisLong long fadeOutDurationMillis,
827                 @IntRange(from = 1) @DurationMillisLong long fadeInDurationMills) {
828             mFadeOutDurationMillis = fadeOutDurationMillis;
829             mFadeInDurationMillis = fadeInDurationMills;
830         }
831 
832         /**
833          * Constructs a new Builder from the given {@link FadeManagerConfiguration}
834          *
835          * @param fmc the {@link FadeManagerConfiguration} object whose data will be reused in the
836          *            new builder
837          */
Builder(@onNull FadeManagerConfiguration fmc)838         public Builder(@NonNull FadeManagerConfiguration fmc) {
839             mFadeState = fmc.mFadeState;
840             copyUsageToFadeWrapperMapInternal(fmc.mUsageToFadeWrapperMap);
841             mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>(
842                     fmc.mAttrToFadeWrapperMap);
843             mFadeableUsages = fmc.mFadeableUsages.clone();
844             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
845             mUnfadeableContentTypes = fmc.mUnfadeableContentTypes.clone();
846             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
847             mUnfadeablePlayerTypes = fmc.mUnfadeablePlayerTypes.clone();
848             mUnfadeableUids = fmc.mUnfadeableUids.clone();
849             mUnfadeableAudioAttributes = new ArrayList<>(fmc.mUnfadeableAudioAttributes);
850             mFadeOutDurationMillis = fmc.mFadeOutDurationMillis;
851             mFadeInDurationMillis = fmc.mFadeInDurationMillis;
852         }
853 
854         /**
855          * Set the overall fade state
856          *
857          * @param state one of the {@link #FADE_STATE_DISABLED} or
858          *     {@link #FADE_STATE_ENABLED_DEFAULT} states
859          * @return the same Builder instance
860          * @throws IllegalArgumentException if the fade state is invalid
861          * @see #getFadeState()
862          */
863         @NonNull
setFadeState(@adeStateEnum int state)864         public Builder setFadeState(@FadeStateEnum int state) {
865             validateFadeState(state);
866             mFadeState = state;
867             return this;
868         }
869 
870         /**
871          * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
872          * {@link android.media.AudioAttributes usage}
873          * <p>
874          * This method accepts {@code null} for volume shaper config to clear a previously set
875          * configuration (example, if set through
876          * {@link #Builder(android.media.FadeManagerConfiguration)})
877          *
878          * @param usage the {@link android.media.AudioAttributes usage} of target player
879          * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
880          *     to fade out players with usage
881          * @return the same Builder instance
882          * @throws IllegalArgumentException if the usage is invalid
883          * @see #getFadeOutVolumeShaperConfigForUsage(int)
884          */
885         @NonNull
setFadeOutVolumeShaperConfigForUsage( @udioAttributes.AttributeUsage int usage, @Nullable VolumeShaper.Configuration fadeOutVShaperConfig)886         public Builder setFadeOutVolumeShaperConfigForUsage(
887                 @AudioAttributes.AttributeUsage int usage,
888                 @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
889             validateUsage(usage);
890             getFadeVolShaperConfigWrapperForUsage(usage)
891                     .setFadeOutVolShaperConfig(fadeOutVShaperConfig);
892             cleanupInactiveWrapperEntries(usage);
893             return this;
894         }
895 
896         /**
897          * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
898          * {@link android.media.AudioAttributes usage}
899          * <p>
900          * This method accepts {@code null} for volume shaper config to clear a previously set
901          * configuration (example, if set through
902          * {@link #Builder(android.media.FadeManagerConfiguration)})
903          *
904          * @param usage the {@link android.media.AudioAttributes usage}
905          * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
906          *     to fade in players with usage
907          * @return the same Builder instance
908          * @throws IllegalArgumentException if the usage is invalid
909          * @see #getFadeInVolumeShaperConfigForUsage(int)
910          */
911         @NonNull
setFadeInVolumeShaperConfigForUsage( @udioAttributes.AttributeUsage int usage, @Nullable VolumeShaper.Configuration fadeInVShaperConfig)912         public Builder setFadeInVolumeShaperConfigForUsage(
913                 @AudioAttributes.AttributeUsage int usage,
914                 @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
915             validateUsage(usage);
916             getFadeVolShaperConfigWrapperForUsage(usage)
917                     .setFadeInVolShaperConfig(fadeInVShaperConfig);
918             cleanupInactiveWrapperEntries(usage);
919             return this;
920         }
921 
922         /**
923          * Set the duration used for fading out players with
924          * {@link android.media.AudioAttributes usage}
925          * <p>
926          * A Volume shaper configuration is generated with the provided duration and default
927          * volume curve definitions. This config is then used to fade out players with given usage.
928          * <p>
929          * In order to clear previously set duration (example, if set through
930          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
931          * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
932          * {@code null}
933          *
934          * @param usage the {@link android.media.AudioAttributes usage} of target player
935          * @param fadeOutDurationMillis positive duration in milliseconds or
936          *     {@link #DURATION_NOT_SET}
937          * @return the same Builder instance
938          * @throws IllegalArgumentException if the fade out duration is non-positive with the
939          *     exception of {@link #DURATION_NOT_SET}
940          * @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
941          * @see #getFadeOutDurationForUsage(int)
942          */
943         @NonNull
setFadeOutDurationForUsage(@udioAttributes.AttributeUsage int usage, @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis)944         public Builder setFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage,
945                 @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis) {
946             validateUsage(usage);
947             VolumeShaper.Configuration fadeOutVShaperConfig =
948                     createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
949             setFadeOutVolumeShaperConfigForUsage(usage, fadeOutVShaperConfig);
950             return this;
951         }
952 
953         /**
954          * Set the duration used for fading in players with
955          * {@link android.media.AudioAttributes usage}
956          * <p>
957          * A Volume shaper configuration is generated with the provided duration and default
958          * volume curve definitions. This config is then used to fade in players with given usage.
959          * <p>
960          * <b>Note: </b>In order to clear previously set duration (example, if set through
961          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
962          * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
963          * {@code null}
964          *
965          * @param usage the {@link android.media.AudioAttributes usage} of target player
966          * @param fadeInDurationMillis positive duration in milliseconds or
967          *     {@link #DURATION_NOT_SET}
968          * @return the same Builder instance
969          * @throws IllegalArgumentException if the fade in duration is non-positive with the
970          *     exception of {@link #DURATION_NOT_SET}
971          * @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
972          * @see #getFadeInDurationForUsage(int)
973          */
974         @NonNull
setFadeInDurationForUsage(@udioAttributes.AttributeUsage int usage, @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis)975         public Builder setFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage,
976                 @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis) {
977             validateUsage(usage);
978             VolumeShaper.Configuration fadeInVShaperConfig =
979                     createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
980             setFadeInVolumeShaperConfigForUsage(usage, fadeInVShaperConfig);
981             return this;
982         }
983 
984         /**
985          * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
986          * {@link android.media.AudioAttributes}
987          * <p>
988          * This method accepts {@code null} for volume shaper config to clear a previously set
989          * configuration (example, set through
990          * {@link #Builder(android.media.FadeManagerConfiguration)})
991          *
992          * @param audioAttributes the {@link android.media.AudioAttributes}
993          * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
994          *     fade out players with audio attribute
995          * @return the same Builder instance
996          * @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes)
997          */
998         @NonNull
setFadeOutVolumeShaperConfigForAudioAttributes( @onNull AudioAttributes audioAttributes, @Nullable VolumeShaper.Configuration fadeOutVShaperConfig)999         public Builder setFadeOutVolumeShaperConfigForAudioAttributes(
1000                 @NonNull AudioAttributes audioAttributes,
1001                 @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
1002             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1003             getFadeVolShaperConfigWrapperForAttr(audioAttributes)
1004                     .setFadeOutVolShaperConfig(fadeOutVShaperConfig);
1005             cleanupInactiveWrapperEntries(audioAttributes);
1006             return this;
1007         }
1008 
1009         /**
1010          * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
1011          * {@link android.media.AudioAttributes}
1012          *
1013          * <p>This method accepts {@code null} for volume shaper config to clear a previously set
1014          * configuration (example, set through
1015          * {@link #Builder(android.media.FadeManagerConfiguration)})
1016          *
1017          * @param audioAttributes the {@link android.media.AudioAttributes}
1018          * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
1019          *     fade in players with audio attribute
1020          * @return the same Builder instance
1021          * @throws NullPointerException if the audio attributes is {@code null}
1022          * @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes)
1023          */
1024         @NonNull
setFadeInVolumeShaperConfigForAudioAttributes( @onNull AudioAttributes audioAttributes, @Nullable VolumeShaper.Configuration fadeInVShaperConfig)1025         public Builder setFadeInVolumeShaperConfigForAudioAttributes(
1026                 @NonNull AudioAttributes audioAttributes,
1027                 @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
1028             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1029             getFadeVolShaperConfigWrapperForAttr(audioAttributes)
1030                     .setFadeInVolShaperConfig(fadeInVShaperConfig);
1031             cleanupInactiveWrapperEntries(audioAttributes);
1032             return this;
1033         }
1034 
1035         /**
1036          * Set the duration used for fading out players of type
1037          * {@link android.media.AudioAttributes}.
1038          * <p>
1039          * A Volume shaper configuration is generated with the provided duration and default
1040          * volume curve definitions. This config is then used to fade out players with given usage.
1041          * <p>
1042          * <b>Note: </b>In order to clear previously set duration (example, if set through
1043          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
1044          * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
1045          * {@code null}
1046          *
1047          * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out
1048          *     duration will be set/updated/reset
1049          * @param fadeOutDurationMillis positive duration in milliseconds or
1050          *     {@link #DURATION_NOT_SET}
1051          * @return the same Builder instance
1052          * @throws IllegalArgumentException if the fade out duration is non-positive with the
1053          *     exception of {@link #DURATION_NOT_SET}
1054          * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
1055          * @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
1056          * VolumeShaper.Configuration)
1057          */
1058         @NonNull
setFadeOutDurationForAudioAttributes( @onNull AudioAttributes audioAttributes, @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis)1059         public Builder setFadeOutDurationForAudioAttributes(
1060                 @NonNull AudioAttributes audioAttributes,
1061                 @IntRange(from = 0) @DurationMillisLong long fadeOutDurationMillis) {
1062             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1063             VolumeShaper.Configuration fadeOutVShaperConfig =
1064                     createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
1065             setFadeOutVolumeShaperConfigForAudioAttributes(audioAttributes, fadeOutVShaperConfig);
1066             return this;
1067         }
1068 
1069         /**
1070          * Set the duration used for fading in players of type {@link android.media.AudioAttributes}
1071          * <p>
1072          * A Volume shaper configuration is generated with the provided duration and default
1073          * volume curve definitions. This config is then used to fade in players with given usage.
1074          * <p>
1075          * <b>Note: </b>In order to clear previously set duration (example, if set through
1076          * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
1077          * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
1078          * {@code null}
1079          *
1080          * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in
1081          *     duration will be set/updated/reset
1082          * @param fadeInDurationMillis positive duration in milliseconds or
1083          *     {@link #DURATION_NOT_SET}
1084          * @return the same Builder instance
1085          * @throws IllegalArgumentException if the fade in duration is non-positive with the
1086          *     exception of {@link #DURATION_NOT_SET}
1087          * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
1088          * @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
1089          * VolumeShaper.Configuration)
1090          */
1091         @NonNull
setFadeInDurationForAudioAttributes(@onNull AudioAttributes audioAttributes, @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis)1092         public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes,
1093                 @IntRange(from = 0) @DurationMillisLong long fadeInDurationMillis) {
1094             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
1095             VolumeShaper.Configuration fadeInVShaperConfig =
1096                     createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
1097             setFadeInVolumeShaperConfigForAudioAttributes(audioAttributes, fadeInVShaperConfig);
1098             return this;
1099         }
1100 
1101         /**
1102          * Set the list of {@link android.media.AudioAttributes usage} that can be faded
1103          *
1104          * <p>This is a positive list. Players with matching usage will be considered for fading.
1105          * Usages that are not part of this list will not be faded
1106          *
1107          * <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one
1108          * usage to be set/added. Failure to do so will result in an exception during
1109          * {@link #build()}
1110          *
1111          * @param usages List of the {@link android.media.AudioAttributes usages}
1112          * @return the same Builder instance
1113          * @throws IllegalArgumentException if the usages are invalid
1114          * @see #getFadeableUsages()
1115          */
1116         @NonNull
setFadeableUsages(@onNull List<Integer> usages)1117         public Builder setFadeableUsages(@NonNull List<Integer> usages) {
1118             Objects.requireNonNull(usages, "List of usages cannot be null");
1119             validateUsages(usages);
1120             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
1121             mFadeableUsages.clear();
1122             mFadeableUsages.addAll(convertIntegerListToIntArray(usages));
1123             return this;
1124         }
1125 
1126         /**
1127          * Add the {@link android.media.AudioAttributes usage} to the fadeable list
1128          *
1129          * @param usage the {@link android.media.AudioAttributes usage}
1130          * @return the same Builder instance
1131          * @throws IllegalArgumentException if the usage is invalid
1132          * @see #getFadeableUsages()
1133          * @see #setFadeableUsages(List)
1134          */
1135         @NonNull
addFadeableUsage(@udioAttributes.AttributeUsage int usage)1136         public Builder addFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
1137             validateUsage(usage);
1138             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
1139             if (!mFadeableUsages.contains(usage)) {
1140                 mFadeableUsages.add(usage);
1141             }
1142             return this;
1143         }
1144 
1145         /**
1146          * Clears the fadeable {@link android.media.AudioAttributes usage} list
1147          *
1148          * <p>This can be used to reset the list when using a copy constructor
1149          *
1150          * @return the same Builder instance
1151          * @see #getFadeableUsages()
1152          * @see #setFadeableUsages(List)
1153          */
1154         @NonNull
clearFadeableUsages()1155         public Builder clearFadeableUsages() {
1156             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
1157             mFadeableUsages.clear();
1158             return this;
1159         }
1160 
1161         /**
1162          * Set the list of {@link android.media.AudioAttributes content type} that can not be faded
1163          *
1164          * <p>This is a negative list. Players with matching content type of this list will not be
1165          * faded. Content types that are not part of this list will be considered for fading.
1166          *
1167          * <p>Passing an empty list as input clears the existing list. This can be used to
1168          * reset the list when using a copy constructor
1169          *
1170          * @param contentTypes list of {@link android.media.AudioAttributes content types}
1171          * @return the same Builder instance
1172          * @throws IllegalArgumentException if the content types are invalid
1173          * @see #getUnfadeableContentTypes()
1174          */
1175         @NonNull
setUnfadeableContentTypes(@onNull List<Integer> contentTypes)1176         public Builder setUnfadeableContentTypes(@NonNull List<Integer> contentTypes) {
1177             Objects.requireNonNull(contentTypes, "List of content types cannot be null");
1178             validateContentTypes(contentTypes);
1179             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
1180             mUnfadeableContentTypes.clear();
1181             mUnfadeableContentTypes.addAll(convertIntegerListToIntArray(contentTypes));
1182             return this;
1183         }
1184 
1185         /**
1186          * Add the {@link android.media.AudioAttributes content type} to unfadeable list
1187          *
1188          * @param contentType the {@link android.media.AudioAttributes content type}
1189          * @return the same Builder instance
1190          * @throws IllegalArgumentException if the content type is invalid
1191          * @see #setUnfadeableContentTypes(List)
1192          * @see #getUnfadeableContentTypes()
1193          */
1194         @NonNull
addUnfadeableContentType( @udioAttributes.AttributeContentType int contentType)1195         public Builder addUnfadeableContentType(
1196                 @AudioAttributes.AttributeContentType int contentType) {
1197             validateContentType(contentType);
1198             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
1199             if (!mUnfadeableContentTypes.contains(contentType)) {
1200                 mUnfadeableContentTypes.add(contentType);
1201             }
1202             return this;
1203         }
1204 
1205         /**
1206          * Clears the unfadeable {@link android.media.AudioAttributes content type} list
1207          *
1208          * <p>This can be used to reset the list when using a copy constructor
1209          *
1210          * @return the same Builder instance
1211          * @see #setUnfadeableContentTypes(List)
1212          * @see #getUnfadeableContentTypes()
1213          */
1214         @NonNull
clearUnfadeableContentTypes()1215         public Builder clearUnfadeableContentTypes() {
1216             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
1217             mUnfadeableContentTypes.clear();
1218             return this;
1219         }
1220 
1221         /**
1222          * Set the uids that cannot be faded
1223          *
1224          * <p>This is a negative list. Players with matching uid of this list will not be faded.
1225          * Uids that are not part of this list shall be considered for fading.
1226          *
1227          * @param uids list of uids
1228          * @return the same Builder instance
1229          * @see #getUnfadeableUids()
1230          */
1231         @NonNull
setUnfadeableUids(@onNull List<Integer> uids)1232         public Builder setUnfadeableUids(@NonNull List<Integer> uids) {
1233             Objects.requireNonNull(uids, "List of uids cannot be null");
1234             mUnfadeableUids.clear();
1235             mUnfadeableUids.addAll(convertIntegerListToIntArray(uids));
1236             return this;
1237         }
1238 
1239         /**
1240          * Add uid to unfadeable list
1241          *
1242          * @param uid client uid
1243          * @return the same Builder instance
1244          * @see #setUnfadeableUids(List)
1245          * @see #getUnfadeableUids()
1246          */
1247         @NonNull
addUnfadeableUid(int uid)1248         public Builder addUnfadeableUid(int uid) {
1249             if (!mUnfadeableUids.contains(uid)) {
1250                 mUnfadeableUids.add(uid);
1251             }
1252             return this;
1253         }
1254 
1255         /**
1256          * Clears the unfadeable uid list
1257          *
1258          * <p>This can be used to reset the list when using a copy constructor.
1259          *
1260          * @return the same Builder instance
1261          * @see #setUnfadeableUids(List)
1262          * @see #getUnfadeableUids()
1263          */
1264         @NonNull
clearUnfadeableUids()1265         public Builder clearUnfadeableUids() {
1266             mUnfadeableUids.clear();
1267             return this;
1268         }
1269 
1270         /**
1271          * Set the list of {@link android.media.AudioAttributes} that can not be faded
1272          *
1273          * <p>This is a negative list. Players with matching audio attributes of this list will not
1274          * be faded. Audio attributes that are not part of this list shall be considered for fading.
1275          *
1276          * <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can
1277          * negatively impact fadeability decision (if such an audio attribute and corresponding
1278          * usage fall into opposing lists).
1279          * For example:
1280          * <pre class=prettyprint>
1281          *    AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre>
1282          * is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}.
1283          * It is an undefined behavior to have an {@link android.media.AudioAttributes usage} in the
1284          * fadeable usage list and the corresponding generic {@link android.media.AudioAttributes}
1285          * in the unfadeable list. Such cases will result in an exception during {@link #build()}.
1286          *
1287          * @param attrs list of {@link android.media.AudioAttributes}
1288          * @return the same Builder instance
1289          * @see #getUnfadeableAudioAttributes()
1290          */
1291         @NonNull
setUnfadeableAudioAttributes(@onNull List<AudioAttributes> attrs)1292         public Builder setUnfadeableAudioAttributes(@NonNull List<AudioAttributes> attrs) {
1293             Objects.requireNonNull(attrs, "List of audio attributes cannot be null");
1294             mUnfadeableAudioAttributes.clear();
1295             mUnfadeableAudioAttributes.addAll(attrs);
1296             return this;
1297         }
1298 
1299         /**
1300          * Add the {@link android.media.AudioAttributes} to the unfadeable list
1301          *
1302          * @param audioAttributes the {@link android.media.AudioAttributes}
1303          * @return the same Builder instance
1304          * @see #setUnfadeableAudioAttributes(List)
1305          * @see #getUnfadeableAudioAttributes()
1306          */
1307         @NonNull
addUnfadeableAudioAttributes(@onNull AudioAttributes audioAttributes)1308         public Builder addUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
1309             Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
1310             if (!mUnfadeableAudioAttributes.contains(audioAttributes)) {
1311                 mUnfadeableAudioAttributes.add(audioAttributes);
1312             }
1313             return this;
1314         }
1315 
1316         /**
1317          * Clears the unfadeable {@link android.media.AudioAttributes} list.
1318          *
1319          * <p>This can be used to reset the list when using a copy constructor.
1320          *
1321          * @return the same Builder instance
1322          * @see #getUnfadeableAudioAttributes()
1323          */
1324         @NonNull
clearUnfadeableAudioAttributes()1325         public Builder clearUnfadeableAudioAttributes() {
1326             mUnfadeableAudioAttributes.clear();
1327             return this;
1328         }
1329 
1330         /**
1331          * Set the delay after which the offending faded out player will be faded in.
1332          *
1333          * <p>This is the amount of time between the app being notified of the focus loss (when its
1334          * muted by the fade out), and the time fade in (to unmute) starts
1335          *
1336          * @param delayMillis delay in milliseconds
1337          * @return the same Builder instance
1338          * @throws IllegalArgumentException if the delay is negative
1339          * @see #getFadeInDelayForOffenders()
1340          */
1341         @NonNull
setFadeInDelayForOffenders( @ntRangefrom = 0) @urationMillisLong long delayMillis)1342         public Builder setFadeInDelayForOffenders(
1343                 @IntRange(from = 0) @DurationMillisLong long delayMillis) {
1344             Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative");
1345             mFadeInDelayForOffendersMillis = delayMillis;
1346             return this;
1347         }
1348 
1349         /**
1350          * Builds the {@link FadeManagerConfiguration} with all of the fade configurations that
1351          * have been set.
1352          *
1353          * @return a new {@link FadeManagerConfiguration} object
1354          */
1355         @NonNull
build()1356         public FadeManagerConfiguration build() {
1357             if (!checkNotSet(IS_BUILDER_USED_FIELD_SET)) {
1358                 throw new IllegalStateException(
1359                         "This Builder should not be reused. Use a new Builder instance instead");
1360             }
1361 
1362             setFlag(IS_BUILDER_USED_FIELD_SET);
1363 
1364             if (checkNotSet(IS_FADEABLE_USAGES_FIELD_SET)) {
1365                 mFadeableUsages = DEFAULT_FADEABLE_USAGES;
1366                 setVolShaperConfigsForUsages(mFadeableUsages);
1367             }
1368 
1369             if (checkNotSet(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET)) {
1370                 mUnfadeableContentTypes = DEFAULT_UNFADEABLE_CONTENT_TYPES;
1371             }
1372 
1373             validateFadeConfigurations();
1374 
1375             return new FadeManagerConfiguration(mFadeState, mFadeOutDurationMillis,
1376                     mFadeInDurationMillis, mFadeInDelayForOffendersMillis, mUsageToFadeWrapperMap,
1377                     mAttrToFadeWrapperMap, mFadeableUsages, mUnfadeableContentTypes,
1378                     mUnfadeablePlayerTypes, mUnfadeableUids, mUnfadeableAudioAttributes);
1379         }
1380 
setFlag(long flag)1381         private void setFlag(long flag) {
1382             mBuilderFieldsSet |= flag;
1383         }
1384 
checkNotSet(long flag)1385         private boolean checkNotSet(long flag) {
1386             return (mBuilderFieldsSet & flag) == 0;
1387         }
1388 
getFadeVolShaperConfigWrapperForUsage(int usage)1389         private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForUsage(int usage) {
1390             if (!mUsageToFadeWrapperMap.contains(usage)) {
1391                 mUsageToFadeWrapperMap.put(usage, new FadeVolumeShaperConfigsWrapper());
1392             }
1393             return mUsageToFadeWrapperMap.get(usage);
1394         }
1395 
getFadeVolShaperConfigWrapperForAttr( AudioAttributes attr)1396         private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForAttr(
1397                 AudioAttributes attr) {
1398             // if no entry, create a new one for setting/clearing
1399             if (!mAttrToFadeWrapperMap.containsKey(attr)) {
1400                 mAttrToFadeWrapperMap.put(attr, new FadeVolumeShaperConfigsWrapper());
1401             }
1402             return mAttrToFadeWrapperMap.get(attr);
1403         }
1404 
createVolShaperConfigForDuration(long duration, boolean isFadeIn)1405         private VolumeShaper.Configuration createVolShaperConfigForDuration(long duration,
1406                 boolean isFadeIn) {
1407             // used to reset the volume shaper config setting
1408             if (duration == DURATION_NOT_SET) {
1409                 return null;
1410             }
1411 
1412             VolumeShaper.Configuration.Builder builder = new VolumeShaper.Configuration.Builder()
1413                     .setId(VOLUME_SHAPER_SYSTEM_FADE_ID)
1414                     .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
1415                     .setDuration(duration);
1416 
1417             if (isFadeIn) {
1418                 builder.setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
1419                         /* volumes= */ new float[]{0.f, 0.30f, 1.0f});
1420             } else {
1421                 builder.setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
1422                         /* volumes= */ new float[]{1.f, 0.65f, 0.0f});
1423             }
1424 
1425             return builder.build();
1426         }
1427 
cleanupInactiveWrapperEntries(int usage)1428         private void cleanupInactiveWrapperEntries(int usage) {
1429             FadeVolumeShaperConfigsWrapper fmcw = mUsageToFadeWrapperMap.get(usage);
1430             // cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
1431             if (fmcw != null && fmcw.isInactive()) {
1432                 mUsageToFadeWrapperMap.remove(usage);
1433             }
1434         }
1435 
cleanupInactiveWrapperEntries(AudioAttributes attr)1436         private void cleanupInactiveWrapperEntries(AudioAttributes attr) {
1437             FadeVolumeShaperConfigsWrapper fmcw = mAttrToFadeWrapperMap.get(attr);
1438             // cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
1439             if (fmcw != null && fmcw.isInactive()) {
1440                 mAttrToFadeWrapperMap.remove(attr);
1441             }
1442         }
1443 
setVolShaperConfigsForUsages(IntArray usages)1444         private void setVolShaperConfigsForUsages(IntArray usages) {
1445             // set default volume shaper configs for fadeable usages
1446             for (int index = 0; index < usages.size(); index++) {
1447                 setMissingVolShaperConfigsForWrapper(
1448                         getFadeVolShaperConfigWrapperForUsage(usages.get(index)));
1449             }
1450         }
1451 
setMissingVolShaperConfigsForWrapper(FadeVolumeShaperConfigsWrapper wrapper)1452         private void setMissingVolShaperConfigsForWrapper(FadeVolumeShaperConfigsWrapper wrapper) {
1453             if (!wrapper.isFadeOutConfigActive()) {
1454                 wrapper.setFadeOutVolShaperConfig(createVolShaperConfigForDuration(
1455                         mFadeOutDurationMillis, /* isFadeIn= */ false));
1456             }
1457             if (!wrapper.isFadeInConfigActive()) {
1458                 wrapper.setFadeInVolShaperConfig(createVolShaperConfigForDuration(
1459                         mFadeInDurationMillis, /* isFadeIn= */ true));
1460             }
1461         }
1462 
copyUsageToFadeWrapperMapInternal( SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap)1463         private  void copyUsageToFadeWrapperMapInternal(
1464                 SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap) {
1465             for (int index = 0; index < usageToFadeWrapperMap.size(); index++) {
1466                 mUsageToFadeWrapperMap.put(usageToFadeWrapperMap.keyAt(index),
1467                         new FadeVolumeShaperConfigsWrapper(usageToFadeWrapperMap.valueAt(index)));
1468             }
1469         }
1470 
validateFadeState(int state)1471         private void validateFadeState(int state) {
1472             switch(state) {
1473                 case FADE_STATE_DISABLED:
1474                 case FADE_STATE_ENABLED_DEFAULT:
1475                     break;
1476                 default:
1477                     throw new IllegalArgumentException("Unknown fade state: " + state);
1478             }
1479         }
1480 
validateUsages(List<Integer> usages)1481         private void validateUsages(List<Integer> usages) {
1482             for (int index = 0; index < usages.size(); index++) {
1483                 validateUsage(usages.get(index));
1484             }
1485         }
1486 
validateContentTypes(List<Integer> contentTypes)1487         private void validateContentTypes(List<Integer> contentTypes) {
1488             for (int index = 0; index < contentTypes.size(); index++) {
1489                 validateContentType(contentTypes.get(index));
1490             }
1491         }
1492 
validateContentType(int contentType)1493         private void validateContentType(int contentType) {
1494             Preconditions.checkArgument(AudioAttributes.isSdkContentType(contentType),
1495                     "Invalid content type: ", contentType);
1496         }
1497 
validateFadeConfigurations()1498         private void validateFadeConfigurations() {
1499             validateFadeableUsages();
1500             validateFadeVolumeShaperConfigsWrappers();
1501             validateUnfadeableAudioAttributes();
1502         }
1503 
1504         /** Ensure fadeable usage list meets config requirements */
validateFadeableUsages()1505         private void validateFadeableUsages() {
1506             // ensure at least one fadeable usage
1507             Preconditions.checkArgumentPositive(mFadeableUsages.size(),
1508                     "Fadeable usage list cannot be empty when state set to enabled");
1509             // ensure all fadeable usages have volume shaper configs - both fade in and out
1510             for (int index = 0; index < mFadeableUsages.size(); index++) {
1511                 setMissingVolShaperConfigsForWrapper(
1512                         getFadeVolShaperConfigWrapperForUsage(mFadeableUsages.get(index)));
1513             }
1514         }
1515 
1516         /** Ensure Fade volume shaper config wrappers meet requirements */
validateFadeVolumeShaperConfigsWrappers()1517         private void validateFadeVolumeShaperConfigsWrappers() {
1518             // ensure both fade in & out volume shaper configs are defined for all wrappers
1519             // for usages -
1520             for (int index = 0; index < mUsageToFadeWrapperMap.size(); index++) {
1521                 setMissingVolShaperConfigsForWrapper(
1522                         getFadeVolShaperConfigWrapperForUsage(mUsageToFadeWrapperMap.keyAt(index)));
1523             }
1524 
1525             // for additional audio attributes -
1526             for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
1527                 setMissingVolShaperConfigsForWrapper(
1528                         getFadeVolShaperConfigWrapperForAttr(mAttrToFadeWrapperMap.keyAt(index)));
1529             }
1530         }
1531 
1532         /** Ensure Unfadeable attributes meet configuration requirements */
validateUnfadeableAudioAttributes()1533         private void validateUnfadeableAudioAttributes() {
1534             // ensure no generic AudioAttributes in unfadeable list with matching usage in fadeable
1535             // list. failure results in an undefined behavior as the audio attributes
1536             // shall be both fadeable (because of the usage) and unfadeable at the same time.
1537             for (int index = 0; index < mUnfadeableAudioAttributes.size(); index++) {
1538                 AudioAttributes targetAttr = mUnfadeableAudioAttributes.get(index);
1539                 int usage = targetAttr.getSystemUsage();
1540                 boolean isFadeableUsage = mFadeableUsages.contains(usage);
1541                 // cannot have a generic audio attribute that also is a fadeable usage
1542                 Preconditions.checkArgument(
1543                         !isFadeableUsage || (isFadeableUsage && !isGeneric(targetAttr)),
1544                         "Unfadeable audio attributes cannot be generic of the fadeable usage");
1545             }
1546         }
1547 
isGeneric(AudioAttributes attr)1548         private static boolean isGeneric(AudioAttributes attr) {
1549             return (attr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN
1550                     && attr.getFlags() == 0x0
1551                     && attr.getBundle() == null
1552                     && attr.getTags().isEmpty());
1553         }
1554     }
1555 
1556     private static final class FadeVolumeShaperConfigsWrapper implements Parcelable {
1557         // null volume shaper config refers to either init state or if its cleared/reset
1558         private @Nullable VolumeShaper.Configuration mFadeOutVolShaperConfig;
1559         private @Nullable VolumeShaper.Configuration mFadeInVolShaperConfig;
1560 
FadeVolumeShaperConfigsWrapper()1561         FadeVolumeShaperConfigsWrapper() {}
1562 
FadeVolumeShaperConfigsWrapper(@onNull FadeVolumeShaperConfigsWrapper wrapper)1563         FadeVolumeShaperConfigsWrapper(@NonNull FadeVolumeShaperConfigsWrapper wrapper) {
1564             Objects.requireNonNull(wrapper, "Fade volume shaper configs wrapper cannot be null");
1565             this.mFadeOutVolShaperConfig = wrapper.mFadeOutVolShaperConfig;
1566             this.mFadeInVolShaperConfig = wrapper.mFadeInVolShaperConfig;
1567         }
1568 
setFadeOutVolShaperConfig(@ullable VolumeShaper.Configuration fadeOutConfig)1569         public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) {
1570             mFadeOutVolShaperConfig = fadeOutConfig;
1571         }
1572 
setFadeInVolShaperConfig(@ullable VolumeShaper.Configuration fadeInConfig)1573         public void setFadeInVolShaperConfig(@Nullable VolumeShaper.Configuration fadeInConfig) {
1574             mFadeInVolShaperConfig = fadeInConfig;
1575         }
1576 
1577         /**
1578          * Query fade out volume shaper config
1579          *
1580          * @return configured fade out volume shaper config or {@code null} when initialized/reset
1581          */
1582         @Nullable
getFadeOutVolShaperConfig()1583         public VolumeShaper.Configuration getFadeOutVolShaperConfig() {
1584             return mFadeOutVolShaperConfig;
1585         }
1586 
1587         /**
1588          * Query fade in volume shaper config
1589          *
1590          * @return configured fade in volume shaper config or {@code null} when initialized/reset
1591          */
1592         @Nullable
getFadeInVolShaperConfig()1593         public VolumeShaper.Configuration getFadeInVolShaperConfig() {
1594             return mFadeInVolShaperConfig;
1595         }
1596 
1597         /**
1598          * Wrapper is inactive if both fade out and in configs are cleared.
1599          *
1600          * @return {@code true} if configs are cleared. {@code false} if either of the configs is
1601          * set
1602          */
isInactive()1603         public boolean isInactive() {
1604             return !isFadeOutConfigActive() && !isFadeInConfigActive();
1605         }
1606 
isFadeOutConfigActive()1607         boolean isFadeOutConfigActive() {
1608             return mFadeOutVolShaperConfig != null;
1609         }
1610 
isFadeInConfigActive()1611         boolean isFadeInConfigActive() {
1612             return mFadeInVolShaperConfig != null;
1613         }
1614 
1615         @Override
equals(Object o)1616         public boolean equals(Object o) {
1617             if (this == o) {
1618                 return true;
1619             }
1620 
1621             if (!(o instanceof FadeVolumeShaperConfigsWrapper)) {
1622                 return false;
1623             }
1624 
1625             FadeVolumeShaperConfigsWrapper rhs = (FadeVolumeShaperConfigsWrapper) o;
1626 
1627             if (mFadeInVolShaperConfig == null && rhs.mFadeInVolShaperConfig == null
1628                     && mFadeOutVolShaperConfig == null && rhs.mFadeOutVolShaperConfig == null) {
1629                 return true;
1630             }
1631 
1632             boolean isEqual;
1633             if (mFadeOutVolShaperConfig != null) {
1634                 isEqual = mFadeOutVolShaperConfig.equals(rhs.mFadeOutVolShaperConfig);
1635             } else if (rhs.mFadeOutVolShaperConfig != null) {
1636                 return false;
1637             } else {
1638                 isEqual = true;
1639             }
1640 
1641             if (mFadeInVolShaperConfig != null) {
1642                 isEqual = isEqual && mFadeInVolShaperConfig.equals(rhs.mFadeInVolShaperConfig);
1643             } else if (rhs.mFadeInVolShaperConfig != null) {
1644                 return false;
1645             }
1646 
1647             return isEqual;
1648         }
1649 
1650         @Override
hashCode()1651         public int hashCode() {
1652             return Objects.hash(mFadeOutVolShaperConfig, mFadeInVolShaperConfig);
1653         }
1654 
1655         @Override
describeContents()1656         public int describeContents() {
1657             return 0;
1658         }
1659 
1660         @Override
writeToParcel(@onNull Parcel dest, int flags)1661         public void writeToParcel(@NonNull Parcel dest, int flags) {
1662             mFadeOutVolShaperConfig.writeToParcel(dest, flags);
1663             mFadeInVolShaperConfig.writeToParcel(dest, flags);
1664         }
1665 
1666         /**
1667          * Creates fade volume shaper config wrapper from parcel
1668          *
1669          * @hide
1670          */
1671         @VisibleForTesting()
FadeVolumeShaperConfigsWrapper(Parcel in)1672         FadeVolumeShaperConfigsWrapper(Parcel in) {
1673             mFadeOutVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
1674             mFadeInVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
1675         }
1676 
1677         @NonNull
1678         public static final Creator<FadeVolumeShaperConfigsWrapper> CREATOR = new Creator<>() {
1679             @Override
1680             @NonNull
1681             public FadeVolumeShaperConfigsWrapper createFromParcel(@NonNull Parcel in) {
1682                 return new FadeVolumeShaperConfigsWrapper(in);
1683             }
1684 
1685             @Override
1686             @NonNull
1687             public FadeVolumeShaperConfigsWrapper[] newArray(int size) {
1688                 return new FadeVolumeShaperConfigsWrapper[size];
1689             }
1690         };
1691     }
1692 }
1693 
1694