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