From 595b6b8fde97a6511cd739325a3e53395a3f221f Mon Sep 17 00:00:00 2001 From: borrelli Date: Wed, 29 Aug 2018 06:56:55 -0700 Subject: [PATCH] Add unit tests for AudioFocusManager. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=210714110 --- .../audio/AudioFocusManagerTest.java | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java new file mode 100644 index 0000000000..1cf812559c --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.audio; + +import static com.google.android.exoplayer2.audio.AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY; +import static com.google.android.exoplayer2.audio.AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY; +import static com.google.android.exoplayer2.audio.AudioFocusManager.PLAYER_COMMAND_WAIT_FOR_CALLBACK; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.media.AudioManager; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Player; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowAudioManager; + +/** Unit tests for {@link AudioFocusManager}. */ +@RunWith(RobolectricTestRunner.class) +public class AudioFocusManagerTest { + private static final int NO_COMMAND_RECEIVED = ~PLAYER_COMMAND_WAIT_FOR_CALLBACK; + + private AudioFocusManager audioFocusManager; + private TestPlayerControl testPlayerControl; + + private AudioManager audioManager; + + @Before + public void setUp() { + audioManager = + (AudioManager) RuntimeEnvironment.application.getSystemService(Context.AUDIO_SERVICE); + + testPlayerControl = new TestPlayerControl(); + audioFocusManager = new AudioFocusManager(RuntimeEnvironment.application, testPlayerControl); + } + + @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)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + } + + @Test + public void setAudioAttributes_withUsageAlarm_throwsIllegalArgumentException() { + // 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); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + } + + @Test + public void setAudioAttributes_withUsageMedia_usesAudioFocusGain() { + // Ensure setting media type audio attributes requests AUDIOFOCUS_GAIN. + 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)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request.durationHint).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); + } + + @Test + public void setAudioAttributes_inStateEnded_requestsAudioFocus() { + // Ensure setting audio attributes when player is in STATE_ENDED requests audio focus. + 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_ENDED)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request.durationHint).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); + } + + @Test + public void handlePrepare_afterSetAudioAttributes_setsPlayerCommandPlayWhenReady() { + // Ensure that when playWhenReady is true while the player is IDLE, audio focus is only + // requested after calling handlePrepare. + 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_IDLE)) + .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull(); + assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ true)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + } + + @Test + public void handleSetPlayWhenReady_afterSetAudioAttributes_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); + + assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ false)) + .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)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + } + + @Test + public void onAudioFocusChange_withDuckEnabled_volumeReducedAndRestored() { + // Ensure that the volume multiplier is adjusted when audio focus is lost to + // 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)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + request.listener.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK); + assertThat(testPlayerControl.lastVolumeMultiplier).isLessThan(1.0f); + assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(NO_COMMAND_RECEIVED); + request.listener.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN); + assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f); + } + + @Test + public void onAudioFocusChange_withPausedWhenDucked_sendsCommandWaitForCallback() { + // Ensure that the player is commanded to pause when audio focus is lost with + // AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK and the content type is CONTENT_TYPE_SPEECH. + 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); + + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + request.listener.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK); + assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f); + request.listener.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN); + assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + } + + @Test + public void onAudioFocusChange_withTransientLost_sendsCommandWaitForCallback() { + // 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)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + request.listener.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT); + assertThat(testPlayerControl.lastVolumeMultiplier).isEqualTo(1.0f); + assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + } + + @Test + public void onAudioFocusChange_withAudioFocusLost_sendsDoNotPlayAndAbandondsFocus() { + // Ensure that AUDIOFOCUS_LOSS causes AudioFocusManager to pause playback and 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(); + request.listener.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS); + assertThat(testPlayerControl.lastPlayerCommand).isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()) + .isEqualTo(request.listener); + } + + @Test + 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 + 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 + 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_PLAY_WHEN_READY); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request).isNull(); + + audioFocusManager.handleStop(); + assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); + } + + private static class TestPlayerControl implements AudioFocusManager.PlayerControl { + private float lastVolumeMultiplier = 1.0f; + private int lastPlayerCommand = NO_COMMAND_RECEIVED; + + @Override + public void setVolumeMultiplier(float volumeMultiplier) { + lastVolumeMultiplier = volumeMultiplier; + } + + @Override + public void executePlayerCommand(int playerCommand) { + lastPlayerCommand = playerCommand; + } + } +}