diff --git a/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java index c9aa9e54a8..86ed841f1a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/AudioFocusManager.java @@ -24,7 +24,6 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -134,64 +133,38 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Sets audio attributes that should be used to manage audio focus. * + *
Call {@link #updateAudioFocus(boolean, int)} to update the audio focus based on these + * attributes. + * * @param audioAttributes The audio attributes or {@code null} if audio focus should not be * managed automatically. - * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. - * @param playerState The current player state; {@link ExoPlayer#getPlaybackState()}. - * @return A {@link PlayerCommand} to execute on the player. */ - @PlayerCommand - public int setAudioAttributes( - @Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) { + public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) { if (!Util.areEqual(this.audioAttributes, audioAttributes)) { this.audioAttributes = audioAttributes; focusGain = convertAudioAttributesToFocusGain(audioAttributes); - Assertions.checkArgument( focusGain == C.AUDIOFOCUS_GAIN || focusGain == C.AUDIOFOCUS_NONE, "Automatic handling of audio focus is only available for USAGE_MEDIA and USAGE_GAME."); - if (playWhenReady - && (playerState == Player.STATE_BUFFERING || playerState == Player.STATE_READY)) { - return requestAudioFocus(); - } } - - return playerState == Player.STATE_IDLE - ? handleIdle(playWhenReady) - : handlePrepare(playWhenReady); } /** - * Called by a player as part of {@link ExoPlayer#prepare(MediaSource, boolean, boolean)}. - * - * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. - * @return A {@link PlayerCommand} to execute on the player. - */ - @PlayerCommand - public int handlePrepare(boolean playWhenReady) { - return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY; - } - - /** - * Called by the player as part of {@link ExoPlayer#setPlayWhenReady(boolean)}. + * Called by the player to abandon or request audio focus based on the desired player state. * * @param playWhenReady The desired value of playWhenReady. - * @param playerState The current state of the player. + * @param playbackState The desired playback state. * @return A {@link PlayerCommand} to execute on the player. */ @PlayerCommand - public int handleSetPlayWhenReady(boolean playWhenReady, int playerState) { - if (!playWhenReady) { - abandonAudioFocus(); - return PLAYER_COMMAND_DO_NOT_PLAY; + public int updateAudioFocus(boolean playWhenReady, @Player.State int playbackState) { + if (!shouldHandleAudioFocus(playbackState)) { + if (audioFocusState != AUDIO_FOCUS_STATE_NO_FOCUS) { + abandonAudioFocus(); + } + return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; } - - return playerState == Player.STATE_IDLE ? handleIdle(playWhenReady) : requestAudioFocus(); - } - - /** Called by the player as part of {@link ExoPlayer#stop(boolean)}. */ - public void handleStop() { - abandonAudioFocus(/* forceAbandon= */ true); + return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY; } // Internal methods. @@ -201,22 +174,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return focusListener; } - @PlayerCommand - private int handleIdle(boolean playWhenReady) { - return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; + private boolean shouldHandleAudioFocus(@Player.State int playbackState) { + return playbackState != Player.STATE_IDLE && focusGain == C.AUDIOFOCUS_GAIN; } @PlayerCommand private int requestAudioFocus() { int focusRequestResult; - if (focusGain == C.AUDIOFOCUS_NONE) { - if (audioFocusState != AUDIO_FOCUS_STATE_NO_FOCUS) { - abandonAudioFocus(/* forceAbandon= */ true); - } - return PLAYER_COMMAND_PLAY_WHEN_READY; - } - if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) { if (Util.SDK_INT >= 26) { focusRequestResult = requestAudioFocusV26(); @@ -239,24 +204,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } private void abandonAudioFocus() { - abandonAudioFocus(/* forceAbandon= */ false); - } - - private void abandonAudioFocus(boolean forceAbandon) { - if (focusGain == C.AUDIOFOCUS_NONE && audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) { + if (audioFocusState == AUDIO_FOCUS_STATE_NO_FOCUS) { return; } - - if (focusGain != C.AUDIOFOCUS_GAIN - || audioFocusState == AUDIO_FOCUS_STATE_LOST_FOCUS - || forceAbandon) { - if (Util.SDK_INT >= 26) { - abandonAudioFocusV26(); - } else { - abandonAudioFocusDefault(); - } - audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS; + if (Util.SDK_INT >= 26) { + abandonAudioFocusV26(); + } else { + abandonAudioFocusDefault(); } + audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS; } private int requestAudioFocusDefault() { @@ -312,7 +268,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; */ @C.AudioFocusGain private static int convertAudioAttributesToFocusGain(@Nullable AudioAttributes audioAttributes) { - if (audioAttributes == null) { // Don't handle audio focus. It may be either video only contents or developers // want to have more finer grained control. (e.g. adding audio focus listener) @@ -414,7 +369,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; break; case AUDIO_FOCUS_STATE_LOST_FOCUS: playerControl.executePlayerCommand(PLAYER_COMMAND_DO_NOT_PLAY); - abandonAudioFocus(/* forceAbandon= */ true); + abandonAudioFocus(); break; case AUDIO_FOCUS_STATE_LOSS_TRANSIENT: playerControl.executePlayerCommand(PLAYER_COMMAND_WAIT_FOR_CALLBACK); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index a3c82d68cf..ba26dfd97a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -686,10 +686,10 @@ public class SimpleExoPlayer extends BasePlayer } } + audioFocusManager.setAudioAttributes(handleAudioFocus ? audioAttributes : null); + boolean playWhenReady = getPlayWhenReady(); @AudioFocusManager.PlayerCommand - int playerCommand = - audioFocusManager.setAudioAttributes( - handleAudioFocus ? audioAttributes : null, getPlayWhenReady(), getPlaybackState()); + int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady(getPlayWhenReady(), playerCommand); } @@ -1190,7 +1190,7 @@ public class SimpleExoPlayer extends BasePlayer this.mediaSource = mediaSource; mediaSource.addEventListener(eventHandler, analyticsCollector); @AudioFocusManager.PlayerCommand - int playerCommand = audioFocusManager.handlePrepare(getPlayWhenReady()); + int playerCommand = audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_BUFFERING); updatePlayWhenReady(getPlayWhenReady(), playerCommand); player.prepare(mediaSource, resetPosition, resetState); } @@ -1199,7 +1199,7 @@ public class SimpleExoPlayer extends BasePlayer public void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThread(); @AudioFocusManager.PlayerCommand - int playerCommand = audioFocusManager.handleSetPlayWhenReady(playWhenReady, getPlaybackState()); + int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady(playWhenReady, playerCommand); } @@ -1278,6 +1278,7 @@ public class SimpleExoPlayer extends BasePlayer @Override public void stop(boolean reset) { verifyApplicationThread(); + audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); player.stop(reset); if (mediaSource != null) { mediaSource.removeEventListener(analyticsCollector); @@ -1286,7 +1287,6 @@ public class SimpleExoPlayer extends BasePlayer mediaSource = null; } } - audioFocusManager.handleStop(); currentCues = Collections.emptyList(); } @@ -1294,7 +1294,7 @@ public class SimpleExoPlayer extends BasePlayer public void release() { verifyApplicationThread(); audioBecomingNoisyManager.setEnabled(false); - audioFocusManager.handleStop(); + audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE); wakeLockManager.setStayAwake(false); wifiLockManager.setStayAwake(false); player.release(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java index 9a44d6def6..5509d3ba0a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/AudioFocusManagerTest.java @@ -65,13 +65,11 @@ public class AudioFocusManagerTest { @Test public void setAudioAttributes_withNullUsage_doesNotManageAudioFocus() { // Ensure that NULL audio attributes -> don't manage audio focus - assertThat( - audioFocusManager.setAudioAttributes( - /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_IDLE)) + audioFocusManager.setAudioAttributes(/* audioAttributes= */ null); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE)) .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); - assertThat( - audioFocusManager.setAudioAttributes( - /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); @@ -85,18 +83,17 @@ public class AudioFocusManagerTest { AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); assertThat(request.durationHint).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); // Ensure that setting null audio attributes with audio focus releases audio focus. - assertThat( - audioFocusManager.setAudioAttributes( - /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(/* audioAttributes= */ null); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); AudioManager.OnAudioFocusChangeListener lastRequest = Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener(); @@ -110,18 +107,16 @@ public class AudioFocusManagerTest { AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); assertThat(getAudioFocusGainFromRequest(request)).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); // Ensure that setting null audio attributes with audio focus releases audio focus. - assertThat( - audioFocusManager.setAudioAttributes( - /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(/* audioAttributes= */ null); + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); AudioFocusRequest lastRequest = Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest(); @@ -130,10 +125,10 @@ public class AudioFocusManagerTest { @Test public void setAudioAttributes_withUsageAlarm_throwsIllegalArgumentException() { - // Ensure that audio attributes that map to AUDIOFOCUS_GAIN_TRANSIENT* throw + // Ensure that audio attributes that map to AUDIOFOCUS_GAIN_TRANSIENT* throw. AudioAttributes alarm = new AudioAttributes.Builder().setUsage(C.USAGE_ALARM).build(); try { - audioFocusManager.setAudioAttributes(alarm, /* playWhenReady= */ false, Player.STATE_IDLE); + audioFocusManager.setAudioAttributes(alarm); fail(); } catch (IllegalArgumentException e) { // Expected @@ -147,9 +142,9 @@ public class AudioFocusManagerTest { Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); @@ -163,9 +158,9 @@ public class AudioFocusManagerTest { Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_ENDED)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_ENDED)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); @@ -173,42 +168,131 @@ public class AudioFocusManagerTest { } @Test - public void handlePrepare_afterSetAudioAttributes_setsPlayerCommandPlayWhenReady() { + public void updateAudioFocusFromIdleToBuffering_setsPlayerCommandPlayWhenReady() { // Ensure that when playWhenReady is true while the player is IDLE, audio focus is only - // requested after calling handlePrepare. + // requested after calling prepare (= changing the state to BUFFERING). AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + audioFocusManager.setAudioAttributes(media); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_IDLE)) + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_IDLE)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull(); - assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ true)) + assertThat( + audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_BUFFERING)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(getAudioFocusGainFromRequest(request)).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); } @Test - public void handleSetPlayWhenReady_afterSetAudioAttributes_setsPlayerCommandPlayWhenReady() { + public void updateAudioFocusFromPausedToPlaying_setsPlayerCommandPlayWhenReady() { // Ensure that audio focus is not requested until playWhenReady is true. AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + audioFocusManager.setAudioAttributes(media); - assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ false)) + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull(); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ false, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); - assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull(); - assertThat( - audioFocusManager.handleSetPlayWhenReady(/* playWhenReady= */ true, Player.STATE_READY)) + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(getAudioFocusGainFromRequest(request)).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); + } + + @Test + @Config(maxSdk = 25) + public void updateAudioFocusFromReadyToIdle_abandonsAudioFocus() { + // Ensure that stopping the player (=changing state to idle) abandons audio focus. + AudioAttributes media = + new AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.CONTENT_TYPE_SPEECH) + .build(); + Shadows.shadowOf(audioManager) + .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + audioFocusManager.setAudioAttributes(media); + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); + + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_IDLE)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()) + .isEqualTo(request.listener); + } + + @Test + @Config(minSdk = 26, maxSdk = TARGET_SDK) + public void updateAudioFocusFromReadyToIdle_abandonsAudioFocus_v26() { + // Ensure that stopping the player (=changing state to idle) abandons audio focus. + AudioAttributes media = + new AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.CONTENT_TYPE_SPEECH) + .build(); + Shadows.shadowOf(audioManager) + .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); + + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_IDLE)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()) + .isEqualTo(request.audioFocusRequest); + } + + @Test + @Config(maxSdk = 25) + public void updateAudioFocusFromReadyToIdle_withoutHandlingAudioFocus_isNoOp() { + // Ensure that changing state to idle is a no-op if audio focus isn't handled. + Shadows.shadowOf(audioManager) + .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + audioFocusManager.setAudioAttributes(null); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request).isNull(); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE)) + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); + } + + @Test + @Config(minSdk = 26, maxSdk = TARGET_SDK) + public void updateAudioFocusFromReadyToIdle_withoutHandlingAudioFocus_isNoOp_v26() { + // Ensure that changing state to idle is a no-op if audio focus isn't handled. + Shadows.shadowOf(audioManager) + .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + audioFocusManager.setAudioAttributes(null); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request).isNull(); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ false, Player.STATE_IDLE)) + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); } @Test @@ -217,17 +301,17 @@ public class AudioFocusManagerTest { // AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK, and returns to the default value after focus is // regained. AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); audioFocusManager .getFocusListener() .onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK); + assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f); assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(NO_COMMAND_RECEIVED); audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN); @@ -243,12 +327,11 @@ public class AudioFocusManagerTest { .setUsage(C.USAGE_MEDIA) .setContentType(C.CONTENT_TYPE_SPEECH) .build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); audioFocusManager @@ -265,12 +348,11 @@ public class AudioFocusManagerTest { // Ensure that the player is commanded to pause when audio focus is lost with // AUDIOFOCUS_LOSS_TRANSIENT. AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); audioFocusManager.getFocusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT); @@ -288,12 +370,11 @@ public class AudioFocusManagerTest { .setUsage(C.USAGE_MEDIA) .setContentType(C.CONTENT_TYPE_SPEECH) .build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); @@ -315,12 +396,11 @@ public class AudioFocusManagerTest { .setUsage(C.USAGE_MEDIA) .setContentType(C.CONTENT_TYPE_SPEECH) .build(); - Shadows.shadowOf(audioManager) .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) + audioFocusManager.setAudioAttributes(media); + + assertThat(audioFocusManager.updateAudioFocus(/* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); @@ -330,120 +410,6 @@ public class AudioFocusManagerTest { .isEqualTo(Shadows.shadowOf(audioManager).getLastAudioFocusRequest().audioFocusRequest); } - @Test - @Config(maxSdk = 25) - public void handleStop_withAudioFocus_abandonsAudioFocus() { - // Ensure that handleStop causes AudioFocusManager to abandon audio focus. - AudioAttributes media = - new AudioAttributes.Builder() - .setUsage(C.USAGE_MEDIA) - .setContentType(C.CONTENT_TYPE_SPEECH) - .build(); - - Shadows.shadowOf(audioManager) - .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); - - ShadowAudioManager.AudioFocusRequest request = - Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); - audioFocusManager.handleStop(); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()) - .isEqualTo(request.listener); - } - - @Test - @Config(minSdk = 26, maxSdk = TARGET_SDK) - public void handleStop_withAudioFocus_abandonsAudioFocus_v26() { - // Ensure that handleStop causes AudioFocusManager to abandon audio focus. - AudioAttributes media = - new AudioAttributes.Builder() - .setUsage(C.USAGE_MEDIA) - .setContentType(C.CONTENT_TYPE_SPEECH) - .build(); - - Shadows.shadowOf(audioManager) - .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ true, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); - - ShadowAudioManager.AudioFocusRequest request = - Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); - audioFocusManager.handleStop(); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()) - .isEqualTo(request.audioFocusRequest); - } - - @Test - @Config(maxSdk = 25) - public void handleStop_withoutAudioFocus_stillAbandonsFocus() { - // Ensure that handleStop causes AudioFocusManager to call through to abandon audio focus - // even if focus wasn't requested. - AudioAttributes media = - new AudioAttributes.Builder() - .setUsage(C.USAGE_MEDIA) - .setContentType(C.CONTENT_TYPE_SPEECH) - .build(); - - Shadows.shadowOf(audioManager) - .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - media, /* playWhenReady= */ false, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); - ShadowAudioManager.AudioFocusRequest request = - Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); - assertThat(request).isNull(); - - audioFocusManager.handleStop(); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNotNull(); - } - - @Test - @Config(maxSdk = 25) - public void handleStop_withoutHandlingAudioFocus_isNoOp() { - // Ensure that handleStop is a no-op if audio focus isn't handled. - Shadows.shadowOf(audioManager) - .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); - ShadowAudioManager.AudioFocusRequest request = - Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); - assertThat(request).isNull(); - - audioFocusManager.handleStop(); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); - } - - @Test - @Config(minSdk = 26, maxSdk = TARGET_SDK) - public void handleStop_withoutHandlingAudioFocus_isNoOp_v26() { - // Ensure that handleStop is a no-op if audio focus isn't handled. - Shadows.shadowOf(audioManager) - .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - assertThat( - audioFocusManager.setAudioAttributes( - /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); - ShadowAudioManager.AudioFocusRequest request = - Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); - assertThat(request).isNull(); - - audioFocusManager.handleStop(); - assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusRequest()).isNull(); - } - private int getAudioFocusGainFromRequest(ShadowAudioManager.AudioFocusRequest audioFocusRequest) { return Util.SDK_INT >= 26 ? audioFocusRequest.audioFocusRequest.getFocusGain()