/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioSessionManager.h" #include "AudioSessionRecord.h" #include "MediaController.h" #include "gtest/gtest.h" #include "mozilla/dom/AudioSessionBinding.h" using namespace mozilla::dom; #define CONTROLLER_ID 0 TEST(AudioSessionManager, Override_StoresValueAndIsKeyedByBc) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kFrameA = 1; constexpr uint64_t kFrameB = 2; controller->SetAudioSessionTypeOverride(kFrameA, AudioSessionType::Transient_solo); const AudioSessionRecord* a = controller->GetAudioSessionRecordForTesting(kFrameA); ASSERT_NE(a, nullptr); ASSERT_TRUE(a->GetTypeOverride()); EXPECT_EQ(*a->GetTypeOverride(), AudioSessionType::Transient_solo); // Setting a different override on another BC must not disturb the first. controller->SetAudioSessionTypeOverride(kFrameB, AudioSessionType::Playback); a = controller->GetAudioSessionRecordForTesting(kFrameA); const AudioSessionRecord* b = controller->GetAudioSessionRecordForTesting(kFrameB); ASSERT_NE(a, nullptr); ASSERT_NE(b, nullptr); EXPECT_EQ(*a->GetTypeOverride(), AudioSessionType::Transient_solo); EXPECT_EQ(*b->GetTypeOverride(), AudioSessionType::Playback); } TEST(AudioSessionManager, Override_AutoClearAndAudibilityLifecycle) { // Setting a non-Auto type creates the record. Clearing it via the "auto" // sentinel normalises the override to Nothing; the manager keeps the // record only while something else still occupies it (audibility, a // non-Inactive state) and drops it once it is fully empty. An audibility // cycle drives mState Active and back to Inactive while the override // remains the lone reason to keep the record alive. constexpr AudioSessionType kOverrides[] = { AudioSessionType::Ambient, AudioSessionType::Transient, AudioSessionType::Transient_solo, AudioSessionType::Playback, AudioSessionType::Play_and_record, }; for (auto override : kOverrides) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kFrame = 42; controller->SetAudioSessionTypeOverride(kFrame, override); ASSERT_NE(controller->GetAudioSessionRecordForTesting(kFrame), nullptr); // Auto on a record whose only field was the override drops the record. controller->SetAudioSessionTypeOverride(kFrame, AudioSessionType::Auto); EXPECT_EQ(controller->GetAudioSessionRecordForTesting(kFrame), nullptr); // Re-create the override and cycle audibility on the same browsing // context. The override keeps the record alive across the audibility // transitions; mState reflects the audibility. controller->SetAudioSessionTypeOverride(kFrame, override); controller->NotifyMediaAudibleChanged(kFrame, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); const AudioSessionRecord* rec = controller->GetAudioSessionRecordForTesting(kFrame); ASSERT_NE(rec, nullptr); EXPECT_EQ(rec->GetState(), AudioSessionState::Active); controller->NotifyMediaAudibleChanged(kFrame, MediaAudibleState::eInaudible, ControlType::eControllable, AudioSessionType::Playback); rec = controller->GetAudioSessionRecordForTesting(kFrame); ASSERT_NE(rec, nullptr); EXPECT_EQ(rec->GetState(), AudioSessionState::Inactive); EXPECT_TRUE(rec->GetAudibleAtMs().isNothing()); // The record is now down to the override alone; clearing it drops the // record again. controller->SetAudioSessionTypeOverride(kFrame, AudioSessionType::Auto); EXPECT_EQ(controller->GetAudioSessionRecordForTesting(kFrame), nullptr); } } TEST(AudioSessionManager, Override_ClearAudioSessionForDropsEntry) { constexpr AudioSessionType kOverrides[] = { AudioSessionType::Ambient, AudioSessionType::Transient, AudioSessionType::Transient_solo, AudioSessionType::Playback, AudioSessionType::Play_and_record, }; for (auto override : kOverrides) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kFrame = 7; controller->SetAudioSessionTypeOverride(kFrame, override); ASSERT_NE(controller->GetAudioSessionRecordForTesting(kFrame), nullptr); controller->ClearAudioSessionFor(kFrame); EXPECT_EQ(controller->GetAudioSessionRecordForTesting(kFrame), nullptr); // Clearing a BC that never had an override is a no-op. controller->ClearAudioSessionFor(kFrame + 1); EXPECT_EQ(controller->GetAudioSessionRecordForTesting(kFrame + 1), nullptr); } } TEST(AudioSessionManager, EffectiveTypeForBc_UnknownBcReturnsDefault) { RefPtr controller = new MediaController(CONTROLLER_ID); EXPECT_EQ( controller->GetAudioSessionManagerForTesting()->EffectiveTypeForBc(42), DefaultAudioSessionType()); } TEST(AudioSessionManager, EffectiveTypeForBc_OverrideWinsOverSource) { // Every user-settable override (i.e. every type except Auto, which // normalises to "no override") must take precedence over the source- // derived type, and clearing the override must fall back to the source. RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 5; controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); const AudioSessionManager* mgr = controller->GetAudioSessionManagerForTesting(); EXPECT_EQ(mgr->EffectiveTypeForBc(kBc), AudioSessionType::Playback); constexpr AudioSessionType kOverrides[] = { AudioSessionType::Ambient, AudioSessionType::Transient, AudioSessionType::Transient_solo, AudioSessionType::Playback, AudioSessionType::Play_and_record, }; for (auto override : kOverrides) { controller->SetAudioSessionTypeOverride(kBc, override); EXPECT_EQ(mgr->EffectiveTypeForBc(kBc), override); // Clearing the override falls back to the source-derived type. controller->SetAudioSessionTypeOverride(kBc, AudioSessionType::Auto); EXPECT_EQ(mgr->EffectiveTypeForBc(kBc), AudioSessionType::Playback); } controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eInaudible, ControlType::eControllable, AudioSessionType::Playback); } // Every non-Auto AudioSessionType, used by the iterating tests below. constexpr AudioSessionType kAllAudioSessionTypesExceptAuto[] = { AudioSessionType::Ambient, AudioSessionType::Transient, AudioSessionType::Transient_solo, AudioSessionType::Playback, AudioSessionType::Play_and_record, }; TEST(AudioSessionManager, GetEffectiveAudioSessionType_NoAudibleBcReturnsAuto) { RefPtr controller = new MediaController(CONTROLLER_ID); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), AudioSessionType::Auto); } TEST(AudioSessionManager, GetEffectiveAudioSessionType_SingleAudibleBcReportsSourceType) { // Every audio-session source type, when carried by the only audible BC, // resolves to that same type. for (auto src : kAllAudioSessionTypesExceptAuto) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 1; controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eAudible, ControlType::eControllable, src); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), src) << "src=" << static_cast(src); } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_OverrideWinsAndAutoResetFallsBack) { // For every (source, override) pair, the override drives the surface and // a subsequent Auto-reset falls back to the source-derived type. for (auto src : kAllAudioSessionTypesExceptAuto) { for (auto ovr : kAllAudioSessionTypesExceptAuto) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 1; controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eAudible, ControlType::eControllable, src); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), src); controller->SetAudioSessionTypeOverride(kBc, ovr); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), ovr); controller->SetAudioSessionTypeOverride(kBc, AudioSessionType::Auto); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), src); } } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_TwoExclusiveBcsPickMostRecent) { // Two audible BCs, both exclusive types: the most recently audible wins // regardless of the type ordering on each side. for (auto first : kExclusiveAudioSessionTypes) { for (auto second : kExclusiveAudioSessionTypes) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 1; constexpr uint64_t kBcB = 2; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, first); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, second); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), second); } } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_TwoNonExclusiveBcsPickHighestPriority) { // Two audible BCs, only non-exclusive types: no audio session is selected // per spec, so the chrome surface falls back to the highest-priority // effective type. for (auto first : kNonExclusiveAudioSessionTypes) { for (auto second : kNonExclusiveAudioSessionTypes) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 1; constexpr uint64_t kBcB = 2; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, first); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, second); const AudioSessionType expected = AudioSessionTypePriorityRank(first) >= AudioSessionTypePriorityRank(second) ? first : second; EXPECT_EQ(controller->GetEffectiveAudioSessionType(), expected); } } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_ExclusiveBeatsNonExclusive) { // For every (exclusive, non-exclusive) pair: the non-exclusive BC becomes // audible LAST (so it is the most recent), yet spec selection ignores it // and the exclusive type wins. for (auto exclusive : kExclusiveAudioSessionTypes) { for (auto nonExclusive : kNonExclusiveAudioSessionTypes) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 1; constexpr uint64_t kBcB = 2; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, exclusive); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, nonExclusive); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), exclusive); } } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_HandoffWhenSelectedBcGoesSilent) { // For every (first, second) pair of exclusive types: BcA audible with // `first` then BcB audible with `second`. BcB is selected (most recent). // When BcB goes silent, selection falls back to BcA. When BcA also goes // silent the surface returns Auto. for (auto first : kExclusiveAudioSessionTypes) { for (auto second : kExclusiveAudioSessionTypes) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 1; constexpr uint64_t kBcB = 2; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, first); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, second); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), second); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eInaudible, ControlType::eControllable, second); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), first); controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eInaudible, ControlType::eControllable, first); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), AudioSessionType::Auto); } } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_OverrideSetBeforePlayApplies) { // For every (override, source) pair: the override is set before any // audibility transition; once the BC becomes audible with `source`, the // surface reports the stored override. for (auto override : kAllAudioSessionTypesExceptAuto) { for (auto source : kAllAudioSessionTypesExceptAuto) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 1; controller->SetAudioSessionTypeOverride(kBc, override); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), AudioSessionType::Auto); controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eAudible, ControlType::eControllable, source); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), override); } } } TEST(AudioSessionManager, GetEffectiveAudioSessionType_UncontrollableOnlyBcParticipates) { // An uncontrollable-only audible BC drives the chrome surface for every // source type. for (auto src : kAllAudioSessionTypesExceptAuto) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 1; controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eAudible, ControlType::eUncontrollable, src); EXPECT_EQ(controller->GetEffectiveAudioSessionType(), src); } } TEST(AudioSessionManager, State_DefaultIsInactive) { // A newly-created record starts with mState == Inactive. The field is // the single source of truth for spec state. RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 11; controller->SetAudioSessionTypeOverride(kBc, AudioSessionType::Playback); const AudioSessionRecord* rec = controller->GetAudioSessionRecordForTesting(kBc); ASSERT_NE(rec, nullptr); EXPECT_EQ(rec->GetState(), AudioSessionState::Inactive); } TEST(AudioSessionManager, State_AudibilityDrivesActiveAndCleansUp) { // Audibility drives the per-BC state through the ยง5.2 mutators: becoming // audible transitions the state to Active and becoming inaudible returns // it to Inactive. Once the record has no override and no audibility // timestamp, going Inactive empties the record and the manager removes // it from the map. RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBc = 21; controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); const AudioSessionRecord* rec = controller->GetAudioSessionRecordForTesting(kBc); ASSERT_NE(rec, nullptr); EXPECT_EQ(rec->GetState(), AudioSessionState::Active); controller->NotifyMediaAudibleChanged(kBc, MediaAudibleState::eInaudible, ControlType::eControllable, AudioSessionType::Playback); EXPECT_EQ(controller->GetAudioSessionRecordForTesting(kBc), nullptr); } // Helper: returns the spec state for a BC by reading the record, or // Inactive when no record exists (matching the spec default for a fresh // audio session). static AudioSessionState StateOf(MediaController* aController, uint64_t aBcId) { const AudioSessionRecord* rec = aController->GetAudioSessionRecordForTesting(aBcId); return rec ? rec->GetState() : AudioSessionState::Inactive; } TEST(AudioSessionManager, State_ExclusiveCascadeFromTypeOverride) { // Setting an explicit exclusive type on an audible BC inactivates other // auto-typed exclusive sessions in the same tab. Each user-settable // exclusive type triggers the cascade. for (AudioSessionType exclusive : kExclusiveAudioSessionTypes) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 71; constexpr uint64_t kBcB = 72; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); ASSERT_EQ(StateOf(controller, kBcA), AudioSessionState::Active); ASSERT_EQ(StateOf(controller, kBcB), AudioSessionState::Active); controller->SetAudioSessionTypeOverride(kBcB, exclusive); EXPECT_EQ(StateOf(controller, kBcA), AudioSessionState::Inactive) << "exclusive override " << static_cast(exclusive) << " should evict the auto-typed exclusive session"; EXPECT_EQ(StateOf(controller, kBcB), AudioSessionState::Active); } } TEST(AudioSessionManager, State_NonExclusiveSessionStaysActive) { // A non-exclusive session is never inactivated by the cascade, even when // the just-updated session is exclusive. Verified for every non-exclusive // type in the spec. for (AudioSessionType nonExclusive : kNonExclusiveAudioSessionTypes) { RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 91; constexpr uint64_t kBcB = 92; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, nonExclusive); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Ambient); controller->SetAudioSessionTypeOverride(kBcB, AudioSessionType::Playback); EXPECT_EQ(StateOf(controller, kBcA), AudioSessionState::Active) << "non-exclusive type " << static_cast(nonExclusive) << " should survive the cascade"; EXPECT_EQ(StateOf(controller, kBcB), AudioSessionState::Active); } } TEST(AudioSessionManager, State_TwoAutoTypedExclusiveSessionsCoexist) { // Two auto-typed exclusive sessions coexist: neither evicts the other // because both pages left the user-set type as auto. RefPtr controller = new MediaController(CONTROLLER_ID); constexpr uint64_t kBcA = 81; constexpr uint64_t kBcB = 82; controller->NotifyMediaAudibleChanged(kBcA, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); controller->NotifyMediaAudibleChanged(kBcB, MediaAudibleState::eAudible, ControlType::eControllable, AudioSessionType::Playback); EXPECT_EQ(StateOf(controller, kBcA), AudioSessionState::Active); EXPECT_EQ(StateOf(controller, kBcB), AudioSessionState::Active); }