| /* |
| * Copyright (C) 2023 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.android.car.audio; |
| |
| import static android.car.media.CarAudioManager.INVALID_REQUEST_ID; |
| import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE; |
| |
| import static org.junit.Assert.assertThrows; |
| import static org.mockito.Mockito.when; |
| |
| import android.car.media.CarAudioManager; |
| import android.car.media.IAudioZonesMirrorStatusCallback; |
| import android.car.test.AbstractExpectableTestCase; |
| import android.media.AudioDeviceInfo; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnitRunner; |
| |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| @RunWith(MockitoJUnitRunner.class) |
| public final class CarAudioMirrorRequestHandlerTest extends AbstractExpectableTestCase { |
| |
| private static final int TEST_ZONE_1 = PRIMARY_AUDIO_ZONE + 1; |
| private static final int TEST_ZONE_2 = PRIMARY_AUDIO_ZONE + 2; |
| private static final int TEST_ZONE_3 = PRIMARY_AUDIO_ZONE + 3; |
| public static final int[] TEST_ZONE_IDS = new int[]{TEST_ZONE_1, TEST_ZONE_2}; |
| private CarAudioMirrorRequestHandler mCarAudioMirrorRequestHandler; |
| private TestAudioZonesMirrorStatusCallbackCallback mTestCallback; |
| @Mock |
| private CarAudioDeviceInfo mCarAudioDeviceInfoOne; |
| @Mock |
| private AudioDeviceInfo mAudioDeviceInfoOne; |
| @Mock |
| private CarAudioDeviceInfo mCarAudioDeviceInfoTwo; |
| @Mock |
| private AudioDeviceInfo mAudioDeviceInfoTwo; |
| private List<CarAudioDeviceInfo> mTestCarAudioDeviceInfos; |
| |
| @Before |
| public void setUp() { |
| mCarAudioMirrorRequestHandler = new CarAudioMirrorRequestHandler(); |
| mTestCallback = new TestAudioZonesMirrorStatusCallbackCallback(); |
| mTestCarAudioDeviceInfos = List.of(mCarAudioDeviceInfoOne, mCarAudioDeviceInfoTwo); |
| mCarAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos); |
| when(mCarAudioDeviceInfoOne.getAudioDeviceInfo()).thenReturn(mAudioDeviceInfoOne); |
| when(mCarAudioDeviceInfoTwo.getAudioDeviceInfo()).thenReturn(mAudioDeviceInfoTwo); |
| } |
| |
| @Test |
| public void getUniqueRequestId() { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| |
| expectWithMessage("Unique request id").that(requestId).isEqualTo(0); |
| } |
| @Test |
| public void getUniqueRequestId_multipleTimes() { |
| long requestIdOne = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| |
| long requestIdTwo = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| |
| expectWithMessage("Unique request id one").that(requestIdOne).isEqualTo(0); |
| expectWithMessage("Unique request id two").that(requestIdTwo).isEqualTo(1); |
| } |
| |
| @Test |
| public void registerAudioZonesMirrorStatusCallback_withNullCallback_fails() { |
| NullPointerException thrown = assertThrows(NullPointerException.class, () -> { |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| /* callback= */ null); |
| }); |
| |
| expectWithMessage("Null audio zones mirror status callback register exception") |
| .that(thrown).hasMessageThat().contains("Audio zones mirror status"); |
| } |
| |
| @Test |
| public void unregisterAudioZonesMirrorStatusCallback_withNullCallback_fails() { |
| NullPointerException thrown = assertThrows(NullPointerException.class, () -> { |
| mCarAudioMirrorRequestHandler.unregisterAudioZonesMirrorStatusCallback( |
| /* callback= */ null); |
| }); |
| |
| expectWithMessage("Null audio zones mirror status callback unregister exception") |
| .that(thrown).hasMessageThat().contains("Audio zones mirror status"); |
| } |
| |
| @Test |
| public void registerAudioZonesMirrorStatusCallback() { |
| boolean registered = mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| |
| expectWithMessage("Register status") |
| .that(registered).isTrue(); |
| } |
| |
| @Test |
| public void registerAudioZonesMirrorStatusCallback_withNoMirroringDeviceAddress() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| |
| boolean registered = carAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| |
| expectWithMessage("Register status with mirroring disabled") |
| .that(registered).isFalse(); |
| } |
| |
| @Test |
| public void registerAudioZonesMirrorStatusCallback_reRegisterFails() { |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| |
| boolean registered = mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| |
| expectWithMessage("Re-register status") |
| .that(registered).isTrue(); |
| } |
| |
| @Test |
| public void unregisterAudioZonesMirrorStatusCallback() { |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| |
| boolean unregister = mCarAudioMirrorRequestHandler |
| .unregisterAudioZonesMirrorStatusCallback(mTestCallback); |
| |
| expectWithMessage("Unregister status") |
| .that(unregister).isTrue(); |
| } |
| |
| @Test |
| public void unregisterAudioZonesMirrorStatusCallback_withoutRegister() { |
| boolean unregister = mCarAudioMirrorRequestHandler |
| .unregisterAudioZonesMirrorStatusCallback(mTestCallback); |
| |
| expectWithMessage("Unregister status for non register callback") |
| .that(unregister).isFalse(); |
| } |
| |
| @Test |
| public void unregisterAudioZonesMirrorStatusCallback_afterUnregister() { |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| mCarAudioMirrorRequestHandler.unregisterAudioZonesMirrorStatusCallback( |
| mTestCallback); |
| |
| boolean unregister = mCarAudioMirrorRequestHandler |
| .unregisterAudioZonesMirrorStatusCallback(mTestCallback); |
| |
| expectWithMessage("Unregister status for already unregistered callback") |
| .that(unregister).isFalse(); |
| } |
| |
| @Test |
| public void setMirrorDeviceInfos_withNullMirrorDeviceAddress() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| |
| NullPointerException thrown = assertThrows(NullPointerException.class, () -> { |
| carAudioMirrorRequestHandler.setMirrorDeviceInfos(/* deviceAddress= */ null); |
| }); |
| |
| expectWithMessage("Null mirror device infos exception") |
| .that(thrown).hasMessageThat().contains("Mirror devices"); |
| } |
| |
| @Test |
| public void setMirrorDeviceInfos() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| |
| carAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos); |
| |
| expectWithMessage("Audio mirror enabled status") |
| .that(carAudioMirrorRequestHandler.isMirrorAudioEnabled()).isTrue(); |
| } |
| |
| @Test |
| public void getMirroringDeviceInfos() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| carAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos); |
| |
| expectWithMessage("Audio mirror device infos") |
| .that(carAudioMirrorRequestHandler.getMirroringDeviceInfos()) |
| .containsExactlyElementsIn(mTestCarAudioDeviceInfos); |
| } |
| |
| @Test |
| public void getMirroringDeviceInfos_withEmptyDeviceInfos() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| carAudioMirrorRequestHandler.setMirrorDeviceInfos(List.of()); |
| |
| expectWithMessage("Empty audio mirror device infos") |
| .that(carAudioMirrorRequestHandler.getMirroringDeviceInfos()).isEmpty(); |
| } |
| |
| @Test |
| public void getAudioDeviceInfo() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| carAudioMirrorRequestHandler.setMirrorDeviceInfos(mTestCarAudioDeviceInfos); |
| |
| expectWithMessage("Audio mirror device info") |
| .that(carAudioMirrorRequestHandler.getAudioDeviceInfo()) |
| .isEqualTo(mAudioDeviceInfoOne); |
| } |
| |
| @Test |
| public void getAudioDeviceInfo_withEmptyDeviceInfos() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| carAudioMirrorRequestHandler.setMirrorDeviceInfos(List.of()); |
| |
| expectWithMessage("Null audio mirror device info") |
| .that(carAudioMirrorRequestHandler.getAudioDeviceInfo()).isNull(); |
| } |
| |
| @Test |
| public void isMirrorAudioEnabled_withoutMirrorDeviceAvailable() { |
| CarAudioMirrorRequestHandler carAudioMirrorRequestHandler = |
| new CarAudioMirrorRequestHandler(); |
| |
| expectWithMessage("Audio mirror enabled status, with no mirror device address") |
| .that(carAudioMirrorRequestHandler.isMirrorAudioEnabled()).isFalse(); |
| } |
| |
| @Test |
| public void enableMirrorForZones() { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, TEST_ZONE_IDS); |
| |
| expectWithMessage("Zone configs for request id %s", requestId) |
| .that(mCarAudioMirrorRequestHandler.getMirrorAudioZonesForRequest(requestId)) |
| .asList().containsExactly(TEST_ZONE_1, TEST_ZONE_2); |
| for (int index = 0; index < TEST_ZONE_IDS.length; index++) { |
| int zoneId = TEST_ZONE_IDS[index]; |
| expectWithMessage("Enabled mirror status for audio zone %s", zoneId) |
| .that(mCarAudioMirrorRequestHandler.isMirrorEnabledForZone(zoneId)).isTrue(); |
| } |
| } |
| |
| @Test |
| public void enableMirrorForZones_withNullZoneIds_fails() { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| NullPointerException thrown = assertThrows(NullPointerException.class, () -> { |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(/* audioZones= */ requestId, null); |
| }); |
| |
| expectWithMessage("Null audio zones enabled exception") |
| .that(thrown).hasMessageThat().contains("Mirror audio zones"); |
| } |
| |
| @Test |
| public void enableMirrorForZones_withInvalidRequestId() { |
| IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(INVALID_REQUEST_ID, TEST_ZONE_IDS); |
| }); |
| |
| expectWithMessage("Enable mirror for zones with invalid request id exception") |
| .that(thrown).hasMessageThat().contains("INVALID_REQUEST_ID"); |
| } |
| |
| @Test |
| public void enableMirrorForZones_afterRegisteringCallback() throws Exception { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback(mTestCallback); |
| |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, TEST_ZONE_IDS); |
| |
| mTestCallback.waitForCallback(); |
| expectWithMessage("Audio mirror status after enabled").that(mTestCallback.mStatus) |
| .isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_APPROVED); |
| expectWithMessage("Audio mirror enables zones").that(mTestCallback.mZoneIds) |
| .asList().containsExactly(TEST_ZONE_1, TEST_ZONE_2); |
| } |
| |
| @Test |
| public void isMirrorEnabledForZone_withNoAudioMirrorEnabled() { |
| expectWithMessage("Enabled mirror status for audio zone for not yet enabled zone") |
| .that(mCarAudioMirrorRequestHandler.isMirrorEnabledForZone(TEST_ZONE_1)).isFalse(); |
| } |
| |
| @Test |
| public void getMirrorAudioZonesForAudioZone_withNoAudioMirrorEnabled() { |
| expectWithMessage("Zone configs for not yet enabled zone").that( |
| mCarAudioMirrorRequestHandler.getMirrorAudioZonesForRequest(TEST_ZONE_1)) |
| .isNull(); |
| } |
| |
| @Test |
| public void rejectMirrorForZones() throws Exception { |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback(mTestCallback); |
| |
| mCarAudioMirrorRequestHandler.rejectMirrorForZones(TEST_ZONE_IDS); |
| |
| mTestCallback.waitForCallback(); |
| expectWithMessage("Audio mirror rejected status").that(mTestCallback.mStatus) |
| .isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_REJECTED); |
| expectWithMessage("Audio mirror rejected zones").that(mTestCallback.mZoneIds) |
| .asList().containsExactly(TEST_ZONE_1, TEST_ZONE_2); |
| } |
| |
| @Test |
| public void updateMirrorConfigurationForZones() throws Exception { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback(mTestCallback); |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, TEST_ZONE_IDS); |
| mTestCallback.waitForCallback(); |
| mTestCallback.reset(); |
| |
| mCarAudioMirrorRequestHandler.updateRemoveMirrorConfigurationForZones( |
| /* newConfig= */ requestId, new int[0]); |
| |
| mTestCallback.waitForCallback(); |
| expectWithMessage("Audio mirror status after removed").that(mTestCallback.mStatus) |
| .isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_STOPPED); |
| expectWithMessage("Audio mirror disabled zones").that(mTestCallback.mZoneIds) |
| .asList().containsExactly(TEST_ZONE_1, TEST_ZONE_2); |
| } |
| |
| @Test |
| public void updateMirrorConfigurationForZones_forSingleZone_inThreeZoneConfig() |
| throws Exception { |
| int[] threeZoneConfig = new int[] {TEST_ZONE_1, TEST_ZONE_2, TEST_ZONE_3}; |
| mCarAudioMirrorRequestHandler.registerAudioZonesMirrorStatusCallback(mTestCallback); |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, threeZoneConfig); |
| mTestCallback.waitForCallback(); |
| mTestCallback.reset(); |
| |
| mCarAudioMirrorRequestHandler.updateRemoveMirrorConfigurationForZones(requestId, |
| TEST_ZONE_IDS |
| ); |
| |
| mTestCallback.waitForCallback(); |
| expectWithMessage("Audio mirror status after removed one zone in three zone config") |
| .that(mTestCallback.mStatus).isEqualTo( |
| CarAudioManager.AUDIO_REQUEST_STATUS_STOPPED); |
| expectWithMessage("Audio mirror disabled a single zone in three zone config") |
| .that(mTestCallback.mZoneIds).asList().containsExactly(TEST_ZONE_3); |
| } |
| |
| @Test |
| public void calculateAudioConfigurationAfterRemovingZonesFromRequestId() { |
| int[] threeZoneConfig = new int[] {TEST_ZONE_1, TEST_ZONE_2, TEST_ZONE_3}; |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, threeZoneConfig); |
| |
| int[] newConfig = mCarAudioMirrorRequestHandler |
| .calculateAudioConfigurationAfterRemovingZonesFromRequestId(requestId, TEST_ZONE_IDS |
| ); |
| |
| expectWithMessage("New audio zone configuration after removing two zones") |
| .that(newConfig).asList().containsExactly(TEST_ZONE_3); |
| } |
| |
| @Test |
| public void calculateAudioConfigurationAfterRemovingZonesFromRequestId_withNotYetSetConfig() { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| int[] newConfig = mCarAudioMirrorRequestHandler |
| .calculateAudioConfigurationAfterRemovingZonesFromRequestId(requestId, TEST_ZONE_IDS |
| ); |
| |
| expectWithMessage("New audio zone configuration for not yet set configuration") |
| .that(newConfig).isNull(); |
| } |
| |
| @Test |
| public void verifyValidRequestId_withNoLongerValidRequest() { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, TEST_ZONE_IDS); |
| mCarAudioMirrorRequestHandler.updateRemoveMirrorConfigurationForZones( |
| /* newConfig= */ requestId, new int[0]); |
| |
| IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { |
| mCarAudioMirrorRequestHandler.verifyValidRequestId(requestId); |
| }); |
| |
| expectWithMessage("No longer valid request id verification exception") |
| .that(thrown).hasMessageThat().contains("is not valid"); |
| } |
| |
| @Test |
| public void verifyValidRequestId_withInvalidRequestId() { |
| IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { |
| mCarAudioMirrorRequestHandler.verifyValidRequestId(INVALID_REQUEST_ID); |
| }); |
| |
| expectWithMessage("Invalid request id verification exception") |
| .that(thrown).hasMessageThat().contains("is not valid"); |
| } |
| |
| @Test |
| public void getRequestIdForAudioZone() { |
| long requestId = mCarAudioMirrorRequestHandler.getUniqueRequestId(); |
| mCarAudioMirrorRequestHandler.enableMirrorForZones(requestId, TEST_ZONE_IDS); |
| |
| expectWithMessage("Request id for audio zone") |
| .that(mCarAudioMirrorRequestHandler.getRequestIdForAudioZone(TEST_ZONE_1)) |
| .isEqualTo(requestId); |
| } |
| |
| @Test |
| public void getRequestIdForAudioZone_forNotYetSetZones() { |
| expectWithMessage("Request id for not yet set audio zone") |
| .that(mCarAudioMirrorRequestHandler.getRequestIdForAudioZone(TEST_ZONE_1)) |
| .isEqualTo(INVALID_REQUEST_ID); |
| } |
| |
| private static final class TestAudioZonesMirrorStatusCallbackCallback extends |
| IAudioZonesMirrorStatusCallback.Stub { |
| |
| private static final long TEST_CALLBACK_TIMEOUT_MS = 100; |
| |
| private int[] mZoneIds; |
| private int mStatus; |
| private CountDownLatch mStatusLatch = new CountDownLatch(1); |
| |
| @Override |
| public void onAudioZonesMirrorStatusChanged(int[] zoneIds, int status) { |
| mZoneIds = zoneIds; |
| mStatus = status; |
| mStatusLatch.countDown(); |
| } |
| |
| private void waitForCallback() throws Exception { |
| mStatusLatch.await(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); |
| } |
| |
| public void reset() { |
| mZoneIds = null; |
| mStatus = 0; |
| mStatusLatch = new CountDownLatch(1); |
| } |
| } |
| } |