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