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 com.android.server.audio;
18 
19 import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.media.AudioAttributes;
24 import android.media.AudioManager;
25 import android.media.AudioPlaybackConfiguration;
26 import android.media.FadeManagerConfiguration;
27 import android.media.VolumeShaper;
28 import android.util.Slog;
29 
30 import com.android.internal.annotations.GuardedBy;
31 
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Objects;
35 
36 /**
37  * Class to encapsulate configurations used for fading players
38  */
39 public final class FadeConfigurations {
40     public static final String TAG = "AS.FadeConfigurations";
41 
42     private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
43 
44 
45     /** duration of the fade out curve */
46     private static final long DEFAULT_FADE_OUT_DURATION_MS = 2000;
47     /**
48      * delay after which a faded out player will be faded back in. This will be heard by the
49      * user only in the case of unmuting players that didn't respect audio focus and didn't
50      * stop/pause when their app lost focus.
51      * This is the amount of time between the app being notified of
52      * the focus loss (when its muted by the fade out), and the time fade in (to unmute) starts
53      */
54     private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000;
55 
56     private static final List<Integer> DEFAULT_UNFADEABLE_PLAYER_TYPES = List.of(
57             AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
58             AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
59     );
60 
61     private static final List<Integer> DEFAULT_UNFADEABLE_CONTENT_TYPES = List.of(
62             AudioAttributes.CONTENT_TYPE_SPEECH
63     );
64 
65     private static final List<Integer> DEFAULT_FADEABLE_USAGES = List.of(
66             AudioAttributes.USAGE_GAME,
67             AudioAttributes.USAGE_MEDIA
68     );
69 
70     private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE =
71             new VolumeShaper.Configuration.Builder()
72                     .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
73                     .setCurve(new float[]{0.f, 0.25f, 1.0f} /* times */,
74                             new float[]{1.f, 0.65f, 0.0f} /* volumes */)
75                     .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
76                     .setDuration(DEFAULT_FADE_OUT_DURATION_MS)
77                     .build();
78 
79     private static final int INVALID_UID = -1;
80 
81     private final Object mLock = new Object();
82     @GuardedBy("mLock")
83     private FadeManagerConfiguration mDefaultFadeManagerConfig;
84     @GuardedBy("mLock")
85     private FadeManagerConfiguration mUpdatedFadeManagerConfig;
86     @GuardedBy("mLock")
87     private FadeManagerConfiguration mTransientFadeManagerConfig;
88     /** active fade manager is one of: transient > updated > default */
89     @GuardedBy("mLock")
90     private FadeManagerConfiguration mActiveFadeManagerConfig;
91 
92     /**
93      * Sets the custom fade manager configuration
94      *
95      * @param fadeManagerConfig custom fade manager configuration
96      * @return {@link AudioManager#SUCCESS}  if setting custom fade manager configuration succeeds
97      *     or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration
98      *     feature is disabled)
99      */
setFadeManagerConfiguration( @onNull FadeManagerConfiguration fadeManagerConfig)100     public int setFadeManagerConfiguration(
101             @NonNull FadeManagerConfiguration fadeManagerConfig) {
102         if (!enableFadeManagerConfiguration()) {
103             return AudioManager.ERROR;
104         }
105 
106         synchronized (mLock) {
107             mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
108                     "Fade manager configuration cannot be null");
109             mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
110         }
111         return AudioManager.SUCCESS;
112     }
113 
114     /**
115      * Clears the fade manager configuration that was previously set with
116      * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
117      *
118      * @return {@link AudioManager#SUCCESS}  if previously set fade manager configuration is cleared
119      *     or {@link AudioManager#ERROR} otherwise (example, when fade manager configuration feature
120      *     is disabled)
121      */
clearFadeManagerConfiguration()122     public int clearFadeManagerConfiguration() {
123         if (!enableFadeManagerConfiguration()) {
124             return AudioManager.ERROR;
125         }
126 
127         synchronized (mLock) {
128             mUpdatedFadeManagerConfig = null;
129             mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
130         }
131         return AudioManager.SUCCESS;
132     }
133 
134     /**
135      * Returns the active fade manager configuration
136      *
137      * @return {@code null} if feature is disabled, or the custom fade manager configuration if set,
138      *     or default fade manager configuration if not set.
139      */
140     @Nullable
getFadeManagerConfiguration()141     public FadeManagerConfiguration getFadeManagerConfiguration() {
142         if (!enableFadeManagerConfiguration()) {
143             return null;
144         }
145 
146         synchronized (mLock) {
147             return mActiveFadeManagerConfig;
148         }
149     }
150 
151     /**
152      * Sets the transient fade manager configuration
153      *
154      * @param fadeManagerConfig custom fade manager configuration
155      * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
156      *     or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration is
157      *     disabled)
158      */
setTransientFadeManagerConfiguration( @onNull FadeManagerConfiguration fadeManagerConfig)159     public int setTransientFadeManagerConfiguration(
160             @NonNull FadeManagerConfiguration fadeManagerConfig) {
161         if (!enableFadeManagerConfiguration()) {
162             return AudioManager.ERROR;
163         }
164 
165         synchronized (mLock) {
166             mTransientFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
167                     "Transient FadeManagerConfiguration cannot be null");
168             mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
169         }
170         return AudioManager.SUCCESS;
171     }
172 
173     /**
174      * Clears the transient fade manager configuration that was previously set with
175      * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
176      *
177      * @return {@link AudioManager#SUCCESS} if previously set transient fade manager configuration
178      *     is cleared or {@link AudioManager#ERROR} otherwise (example - when fade manager
179      *     configuration is disabled)
180      */
clearTransientFadeManagerConfiguration()181     public int clearTransientFadeManagerConfiguration() {
182         if (!enableFadeManagerConfiguration()) {
183             return AudioManager.ERROR;
184         }
185         synchronized (mLock) {
186             mTransientFadeManagerConfig = null;
187             mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
188         }
189         return AudioManager.SUCCESS;
190     }
191 
192     /**
193      * Query if fade should be enforecd on players
194      *
195      * @return {@code true} if fade is enabled or using default configurations, {@code false}
196      * otherwise.
197      */
isFadeEnabled()198     public boolean isFadeEnabled() {
199         if (!enableFadeManagerConfiguration()) {
200             return true;
201         }
202 
203         synchronized (mLock) {
204             return getUpdatedFadeManagerConfigLocked().isFadeEnabled();
205         }
206     }
207 
208     /**
209      * Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to
210      * fade
211      *
212      * @return list of {@link android.media.AudioAttributes.AttributeUsage}
213      */
214     @NonNull
getFadeableUsages()215     public List<Integer> getFadeableUsages() {
216         if (!enableFadeManagerConfiguration()) {
217             return DEFAULT_FADEABLE_USAGES;
218         }
219 
220         synchronized (mLock) {
221             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
222             // when fade is not enabled, return an empty list instead
223             return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages()
224                     : Collections.EMPTY_LIST;
225         }
226     }
227 
228     /**
229      * Query {@link android.media.AudioAttributes.AttributeContentType content types} that are
230      * exempted from fade enforcement
231      *
232      * @return list of {@link android.media.AudioAttributes.AttributeContentType}
233      */
234     @NonNull
getUnfadeableContentTypes()235     public List<Integer> getUnfadeableContentTypes() {
236         if (!enableFadeManagerConfiguration()) {
237             return DEFAULT_UNFADEABLE_CONTENT_TYPES;
238         }
239 
240         synchronized (mLock) {
241             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
242             // when fade is not enabled, return an empty list instead
243             return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes()
244                     : Collections.EMPTY_LIST;
245         }
246     }
247 
248     /**
249      * Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are
250      * exempted from fade enforcement
251      *
252      * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
253      */
254     @NonNull
getUnfadeablePlayerTypes()255     public List<Integer> getUnfadeablePlayerTypes() {
256         if (!enableFadeManagerConfiguration()) {
257             return DEFAULT_UNFADEABLE_PLAYER_TYPES;
258         }
259 
260         synchronized (mLock) {
261             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
262             // when fade is not enabled, return an empty list instead
263             return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes()
264                     : Collections.EMPTY_LIST;
265         }
266     }
267 
268     /**
269      * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied
270      * for the fade-out
271      *
272      * @param aa The {@link android.media.AudioAttributes}
273      * @return {@link android.media.VolumeShaper.Configuration} for the
274      * {@link android.media.AudioAttributes} or default volume shaper if not configured
275      */
276     @NonNull
getFadeOutVolumeShaperConfig(@onNull AudioAttributes aa)277     public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) {
278         if (!enableFadeManagerConfiguration()) {
279             return DEFAULT_FADEOUT_VSHAPE;
280         }
281         return getOptimalFadeOutVolShaperConfig(aa);
282     }
283 
284     /**
285      * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the
286      * fade in
287      *
288      * @param aa The {@link android.media.AudioAttributes}
289      * @return {@link android.media.VolumeShaper.Configuration} for the
290      * {@link android.media.AudioAttributes} or {@code null} otherwise
291      */
292     @Nullable
getFadeInVolumeShaperConfig(@onNull AudioAttributes aa)293     public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) {
294         if (!enableFadeManagerConfiguration()) {
295             return null;
296         }
297         return getOptimalFadeInVolShaperConfig(aa);
298     }
299 
300 
301     /**
302      * Get the duration to fade out a player of type usage
303      *
304      * @param aa The {@link android.media.AudioAttributes}
305      * @return duration in milliseconds for the
306      * {@link android.media.AudioAttributes} or default duration if not configured
307      */
getFadeOutDuration(@onNull AudioAttributes aa)308     public long getFadeOutDuration(@NonNull AudioAttributes aa) {
309         if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) {
310             return 0;
311         }
312         if (!enableFadeManagerConfiguration()) {
313             return DEFAULT_FADE_OUT_DURATION_MS;
314         }
315         return getOptimalFadeOutDuration(aa);
316     }
317 
318     /**
319      * Get the delay to fade in offending players that do not stop after losing audio focus
320      *
321      * @param aa The {@link android.media.AudioAttributes}
322      * @return delay in milliseconds for the
323      * {@link android.media.AudioAttributes.Attribute} or default delay if not configured
324      */
getDelayFadeInOffenders(@onNull AudioAttributes aa)325     public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) {
326         if (!enableFadeManagerConfiguration()) {
327             return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
328         }
329 
330         synchronized (mLock) {
331             return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders();
332         }
333     }
334 
335     /**
336      * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement
337      *
338      * @return list of {@link android.media.AudioAttributes}
339      */
340     @NonNull
getUnfadeableAudioAttributes()341     public List<AudioAttributes> getUnfadeableAudioAttributes() {
342         // unfadeable audio attributes is only supported with fade manager configurations
343         if (!enableFadeManagerConfiguration()) {
344             return Collections.EMPTY_LIST;
345         }
346 
347         synchronized (mLock) {
348             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
349             // when fade is not enabled, return empty list
350             return fadeManagerConfig.isFadeEnabled()
351                     ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST;
352         }
353     }
354 
355     /**
356      * Query uids that are exempted from fade enforcement
357      *
358      * @return list of uids
359      */
360     @NonNull
getUnfadeableUids()361     public List<Integer> getUnfadeableUids() {
362         // unfadeable uids is only supported with fade manager configurations
363         if (!enableFadeManagerConfiguration()) {
364             return Collections.EMPTY_LIST;
365         }
366 
367         synchronized (mLock) {
368             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
369             // when fade is not enabled, return empty list
370             return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids()
371                     : Collections.EMPTY_LIST;
372         }
373     }
374 
375     /**
376      * Check if it is allowed to fade for the given {@link android.media.AudioAttributes},
377      * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config
378      *
379      * @param aa The {@link android.media.AudioAttributes}
380      * @param uid The uid of the client owning the player
381      * @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType}
382      * @return {@code true} if it the config is fadeable and {@code false} otherwise
383      */
isFadeable(@onNull AudioAttributes aa, int uid, @AudioPlaybackConfiguration.PlayerType int playerType)384     public boolean isFadeable(@NonNull AudioAttributes aa, int uid,
385             @AudioPlaybackConfiguration.PlayerType int playerType) {
386         synchronized (mLock) {
387             if (isPlayerTypeUnfadeableLocked(playerType)) {
388                 if (DEBUG) {
389                     Slog.i(TAG, "not fadeable: player type:" + playerType);
390                 }
391                 return false;
392             }
393             if (isContentTypeUnfadeableLocked(aa.getContentType())) {
394                 if (DEBUG) {
395                     Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
396                 }
397                 return false;
398             }
399             if (!isUsageFadeableLocked(aa.getSystemUsage())) {
400                 if (DEBUG) {
401                     Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
402                 }
403                 return false;
404             }
405             // new configs using fade manager configuration
406             if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) {
407                 return false;
408             }
409             return true;
410         }
411     }
412 
413     /** Tries to get the fade out volume shaper config closest to the audio attributes */
getOptimalFadeOutVolShaperConfig(AudioAttributes aa)414     private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) {
415         synchronized (mLock) {
416             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
417             // check if the specific audio attributes has a volume shaper config defined
418             VolumeShaper.Configuration volShaperConfig =
419                     fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa);
420             if (volShaperConfig != null) {
421                 return volShaperConfig;
422             }
423 
424             // get the volume shaper config for usage
425             // for fadeable usages, this should never return null
426             return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage(
427                     aa.getSystemUsage());
428         }
429     }
430 
431     /** Tries to get the fade in volume shaper config closest to the audio attributes */
getOptimalFadeInVolShaperConfig(AudioAttributes aa)432     private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) {
433         synchronized (mLock) {
434             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
435             // check if the specific audio attributes has a volume shaper config defined
436             VolumeShaper.Configuration volShaperConfig =
437                     fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa);
438             if (volShaperConfig != null) {
439                 return volShaperConfig;
440             }
441 
442             // get the volume shaper config for usage
443             // for fadeable usages, this should never return null
444             return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage());
445         }
446     }
447 
448     /** Tries to get the duration closest to the audio attributes */
getOptimalFadeOutDuration(AudioAttributes aa)449     private long getOptimalFadeOutDuration(AudioAttributes aa) {
450         synchronized (mLock) {
451             FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
452             // check if specific audio attributes has a duration defined
453             long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa);
454             if (duration != FadeManagerConfiguration.DURATION_NOT_SET) {
455                 return duration;
456             }
457 
458             // get the duration for usage
459             // for fadeable usages, this should never return DURATION_NOT_SET
460             return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage());
461         }
462     }
463 
464     @GuardedBy("mLock")
isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid)465     private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) {
466         if (isAudioAttributesUnfadeableLocked(aa)) {
467             if (DEBUG) {
468                 Slog.i(TAG, "not fadeable: aa:" + aa);
469             }
470             return true;
471         }
472         if (isUidUnfadeableLocked(uid)) {
473             if (DEBUG) {
474                 Slog.i(TAG, "not fadeable: uid:" + uid);
475             }
476             return true;
477         }
478         return false;
479     }
480 
481     @GuardedBy("mLock")
isUsageFadeableLocked(int usage)482     private boolean isUsageFadeableLocked(int usage) {
483         if (!enableFadeManagerConfiguration()) {
484             return DEFAULT_FADEABLE_USAGES.contains(usage);
485         }
486         return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage);
487     }
488 
489     @GuardedBy("mLock")
isContentTypeUnfadeableLocked(int contentType)490     private boolean isContentTypeUnfadeableLocked(int contentType) {
491         if (!enableFadeManagerConfiguration()) {
492             return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType);
493         }
494         return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType);
495     }
496 
497     @GuardedBy("mLock")
isPlayerTypeUnfadeableLocked(int playerType)498     private boolean isPlayerTypeUnfadeableLocked(int playerType) {
499         if (!enableFadeManagerConfiguration()) {
500             return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType);
501         }
502         return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType);
503     }
504 
505     @GuardedBy("mLock")
isAudioAttributesUnfadeableLocked(AudioAttributes aa)506     private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) {
507         if (!enableFadeManagerConfiguration()) {
508             // default fade configs do not support unfadeable audio attributes, hence return false
509             return false;
510         }
511         return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa);
512     }
513 
514     @GuardedBy("mLock")
isUidUnfadeableLocked(int uid)515     private boolean isUidUnfadeableLocked(int uid) {
516         if (!enableFadeManagerConfiguration()) {
517             // default fade configs do not support unfadeable uids, hence return false
518             return false;
519         }
520         return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid);
521     }
522 
523     @GuardedBy("mLock")
getUpdatedFadeManagerConfigLocked()524     private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() {
525         if (mActiveFadeManagerConfig == null) {
526             mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
527         }
528         return mActiveFadeManagerConfig;
529     }
530 
531     /** Priority between fade manager configs: Transient > Updated > Default */
532     @GuardedBy("mLock")
getActiveFadeMgrConfigLocked()533     private FadeManagerConfiguration getActiveFadeMgrConfigLocked() {
534         // below configs are arranged in the order of priority
535         // configs placed higher have higher priority
536         if (mTransientFadeManagerConfig != null) {
537             return mTransientFadeManagerConfig;
538         }
539 
540         if (mUpdatedFadeManagerConfig != null) {
541             return mUpdatedFadeManagerConfig;
542         }
543 
544         // default - must be the lowest priority
545         return getDefaultFadeManagerConfigLocked();
546     }
547 
548     @GuardedBy("mLock")
getDefaultFadeManagerConfigLocked()549     private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() {
550         if (mDefaultFadeManagerConfig == null) {
551             mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build();
552         }
553         return mDefaultFadeManagerConfig;
554     }
555 }
556