From 65436d48fb1ed7868812134821241d4d7e35b96d Mon Sep 17 00:00:00 2001 From: Weilin Xu Date: Wed, 9 Nov 2022 03:37:29 +0000 Subject: [PATCH 1/2] Add unit tests for radio HIDL tuner and module Unit tests for radio module and get/set methods in tuner session of broadcast radio HIDL HAL client are added. Meanwhile, replace deprecated Matchers package with ArgumentMatchers package. Bug: 258034192 Test: atest TunerSessionHidlTest RadioTunerHidlTest Change-Id: I5530c5b42cf3f4763df8d003871196d630ef06cd --- .../IRadioServiceAidlImplTest.java | 4 +- .../IRadioServiceHidlImplTest.java | 4 +- .../broadcastradio/aidl/AidlTestUtils.java | 10 + .../broadcastradio/aidl/RadioModuleTest.java | 15 +- .../broadcastradio/aidl/TunerSessionTest.java | 19 +- .../hal2/RadioModuleHidlTest.java | 151 +++++++++++++++ .../StartProgramListUpdatesFanoutTest.java | 5 +- .../server/broadcastradio/hal2/TestUtils.java | 10 + .../hal2/TunerSessionHidlTest.java | 175 +++++++++++++++++- .../hal2/BroadcastRadioService.java | 2 +- .../broadcastradio/hal2/RadioModule.java | 6 +- 11 files changed, 361 insertions(+), 40 deletions(-) create mode 100644 core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java index 14dd5df1716fa..a2df426c315c8 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java @@ -20,9 +20,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java index f28e27d7f8961..5ab943542f81d 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java @@ -18,9 +18,9 @@ package com.android.server.broadcastradio; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java index 2cb058bd61a39..a4212180d0b50 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java @@ -31,6 +31,16 @@ final class AidlTestUtils { throw new UnsupportedOperationException("AidlTestUtils class is noninstantiable"); } + static RadioManager.ModuleProperties makeDefaultModuleProperties() { + return new RadioManager.ModuleProperties( + /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "", + /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0, + /* numAudioSources= */ 0, /* isInitializationRequired= */ false, + /* isCaptureSupported= */ false, /* bands= */ null, + /* isBgScanSupported= */ false, new int[] {}, new int[] {}, + /* dabFrequencyTable= */ null, /* vendorInfo= */ null); + } + static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) { return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null, diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java index c1a43367a13b1..7a8475fe4d8f4 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java @@ -19,9 +19,9 @@ package com.android.server.broadcastradio.aidl; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -48,14 +48,7 @@ public final class RadioModuleTest { private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT; private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES = - new RadioManager.ModuleProperties(/* id= */ 0, /* serviceName= */ "", /* classId= */ 0, - /* implementor= */ "", /* product= */ "", /* version= */ "", - /* serial= */ "", /* numTuners= */ 0, /* numAudioSources= */ 0, - /* isInitializationRequired= */ false, /* isCaptureSupported= */ false, - /* bands= */ null, /* isBgScanSupported= */ false, - /* supportedProgramTypes= */ new int[]{}, - /* supportedIdentifierTypes */ new int[]{}, - /* dabFrequencyTable= */ null, /* vendorInfo= */ null); + AidlTestUtils.makeDefaultModuleProperties(); // Mocks @Mock @@ -108,7 +101,7 @@ public final class RadioModuleTest { Bitmap imageTest = mRadioModule.getImage(imageId); - assertWithMessage("Image got from radio module").that(imageTest).isNull(); + assertWithMessage("Image from radio module").that(imageTest).isNull(); } @Test diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index c3623a28c17ad..3bf993c07aed6 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -19,10 +19,10 @@ package com.android.server.broadcastradio.aidl; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; @@ -93,13 +93,8 @@ public final class TunerSessionTest { @Before public void setup() throws Exception { - mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties( - /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "", - /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0, - /* numAudioSources= */ 0, /* isInitializationRequired= */ false, - /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false, - new int[] {}, new int[] {}, - /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock); + mRadioModule = new RadioModule(mBroadcastRadioMock, + AidlTestUtils.makeDefaultModuleProperties(), mLock); doAnswer(invocation -> { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; @@ -424,7 +419,7 @@ public final class TunerSessionTest { mTunerSessions[0].getImage(imageId); }); - assertWithMessage("Exception for getting image with invalid ID") + assertWithMessage("Get image exception") .that(thrown).hasMessageThat().contains("Image ID is missing"); } @@ -467,7 +462,7 @@ public final class TunerSessionTest { boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag); verify(mBroadcastRadioMock).isConfigFlagSet(flag); - assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse(); + assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse(); } @Test diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java new file mode 100644 index 0000000000000..48f5a461d631c --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2022 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.server.broadcastradio.hal2; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.Bitmap; +import android.hardware.broadcastradio.V2_0.Constants; +import android.hardware.broadcastradio.V2_0.IBroadcastRadio; +import android.hardware.broadcastradio.V2_0.Result; +import android.hardware.radio.Announcement; +import android.hardware.radio.IAnnouncementListener; +import android.hardware.radio.ICloseHandle; +import android.hardware.radio.RadioManager; +import android.os.RemoteException; + +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.ArrayList; +import java.util.Arrays; + +/** + * Tests for HIDL HAL RadioModule. + */ +@RunWith(MockitoJUnitRunner.class) +public final class RadioModuleHidlTest { + + private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT; + private static final RadioManager.ModuleProperties TEST_MODULE_PROPERTIES = + TestUtils.makeDefaultModuleProperties(); + + @Mock + private IBroadcastRadio mBroadcastRadioMock; + @Mock + private IAnnouncementListener mListenerMock; + @Mock + private android.hardware.broadcastradio.V2_0.ICloseHandle mHalCloseHandleMock; + + private final Object mLock = new Object(); + private RadioModule mRadioModule; + private android.hardware.broadcastradio.V2_0.IAnnouncementListener mHalListener; + + @Before + public void setup() throws RemoteException { + mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock); + + when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList(0)); + + doAnswer(invocation -> { + mHalListener = (android.hardware.broadcastradio.V2_0.IAnnouncementListener) invocation + .getArguments()[1]; + IBroadcastRadio.registerAnnouncementListenerCallback cb = + (IBroadcastRadio.registerAnnouncementListenerCallback) + invocation.getArguments()[2]; + cb.onValues(Result.OK, mHalCloseHandleMock); + return null; + }).when(mBroadcastRadioMock).registerAnnouncementListener(any(), any(), any()); + } + + @Test + public void getService() { + assertWithMessage("Service of radio module") + .that(mRadioModule.getService()).isEqualTo(mBroadcastRadioMock); + } + + @Test + public void getProperties() { + assertWithMessage("Module properties of radio module") + .that(mRadioModule.getProperties()).isEqualTo(TEST_MODULE_PROPERTIES); + } + + @Test + public void getImage_withValidIdFromRadioModule() { + int imageId = 1; + + Bitmap imageTest = mRadioModule.getImage(imageId); + + assertWithMessage("Image from radio module").that(imageTest).isNull(); + } + + @Test + public void getImage_withInvalidIdFromRadioModule_throwsIllegalArgumentException() { + int invalidImageId = Constants.INVALID_IMAGE; + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + mRadioModule.getImage(invalidImageId); + }); + + assertWithMessage("Exception for getting image with invalid ID") + .that(thrown).hasMessageThat().contains("Image ID is missing"); + } + + @Test + public void addAnnouncementListener_listenerRegistered() throws Exception { + ArrayList enabledListExpected = new ArrayList(Arrays.asList( + (byte) TEST_ENABLED_TYPE)); + mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock); + + verify(mBroadcastRadioMock) + .registerAnnouncementListener(eq(enabledListExpected), any(), any()); + } + + @Test + public void onListUpdate_forAnnouncementListener() throws Exception { + android.hardware.broadcastradio.V2_0.Announcement halAnnouncement = + TestUtils.makeAnnouncement(TEST_ENABLED_TYPE, /* selectorFreq= */ 96300); + mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock); + + mHalListener.onListUpdated( + new ArrayList( + Arrays.asList(halAnnouncement))); + + verify(mListenerMock).onListUpdated(any()); + } + + @Test + public void close_forCloseHandle() throws Exception { + ICloseHandle closeHandle = + mRadioModule.addAnnouncementListener(new int[]{TEST_ENABLED_TYPE}, mListenerMock); + + closeHandle.close(); + + verify(mHalCloseHandleMock).close(); + } +} diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java index 25bf93f720885..d1043595535a2 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java @@ -95,9 +95,8 @@ public class StartProgramListUpdatesFanoutTest { public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); - mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(0, "", - 0, "", "", "", "", 0, 0, false, false, null, false, new int[] {}, new int[] {}, - null, null), mLock); + mRadioModule = new RadioModule(mBroadcastRadioMock, + TestUtils.makeDefaultModuleProperties(), mLock); doAnswer((Answer) invocation -> { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java index ad1b8727d8a78..4eedd2fdb369b 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java @@ -33,6 +33,16 @@ final class TestUtils { throw new UnsupportedOperationException("TestUtils class is noninstantiable"); } + static RadioManager.ModuleProperties makeDefaultModuleProperties() { + return new RadioManager.ModuleProperties( + /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "", + /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0, + /* numAudioSources= */ 0, /* isInitializationRequired= */ false, + /* isCaptureSupported= */ false, /* bands= */ null, + /* isBgScanSupported= */ false, new int[] {}, new int[] {}, + /* dabFrequencyTable= */ null, /* vendorInfo= */ null); + } + static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) { return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null, diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index 9b62dc75419fb..936e606fcb76d 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -37,12 +37,15 @@ import android.hardware.broadcastradio.V2_0.ITunerSession; import android.hardware.broadcastradio.V2_0.IdentifierType; import android.hardware.broadcastradio.V2_0.ProgramInfo; import android.hardware.broadcastradio.V2_0.Result; +import android.hardware.broadcastradio.V2_0.VendorKeyValue; import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioTuner; +import android.util.ArrayMap; import android.util.ArraySet; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,6 +54,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.verification.VerificationWithTimeout; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; /** * Tests for HIDL HAL TunerSession. @@ -73,6 +78,7 @@ public final class TunerSessionHidlTest { private static final int UNSUPPORTED_CONFIG_FLAG = 0; private final Object mLock = new Object(); + private final ArrayMap mHalConfigMap = new ArrayMap<>(); private RadioModule mRadioModule; private ITunerCallback mHalTunerCallback; private ProgramInfo mHalCurrentInfo; @@ -84,13 +90,8 @@ public final class TunerSessionHidlTest { @Before public void setup() throws Exception { - mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties( - /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "", - /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0, - /* numAudioSources= */ 0, /* isInitializationRequired= */ false, - /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false, - new int[] {}, new int[] {}, - /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock); + mRadioModule = new RadioModule(mBroadcastRadioMock, + TestUtils.makeDefaultModuleProperties(), mLock); doAnswer(invocation -> { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; @@ -142,6 +143,32 @@ public final class TunerSessionHidlTest { }).when(mHalTunerSessionMock).scan(anyBoolean(), anyBoolean()); when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList(0)); + + doAnswer(invocation -> { + int configFlag = (int) invocation.getArguments()[0]; + ITunerSession.isConfigFlagSetCallback cb = (ITunerSession.isConfigFlagSetCallback) + invocation.getArguments()[1]; + if (configFlag == UNSUPPORTED_CONFIG_FLAG) { + cb.onValues(Result.NOT_SUPPORTED, false); + return null; + } + cb.onValues(Result.OK, mHalConfigMap.getOrDefault(configFlag, false)); + return null; + }).when(mHalTunerSessionMock).isConfigFlagSet(anyInt(), any()); + + doAnswer(invocation -> { + int configFlag = (int) invocation.getArguments()[0]; + if (configFlag == UNSUPPORTED_CONFIG_FLAG) { + return Result.NOT_SUPPORTED; + } + mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]); + return Result.OK; + }).when(mHalTunerSessionMock).setConfigFlag(anyInt(), anyBoolean()); + } + + @After + public void cleanUp() { + mHalConfigMap.clear(); } @Test @@ -395,7 +422,7 @@ public final class TunerSessionHidlTest { mTunerSessions[0].getImage(imageId); }); - assertWithMessage("Exception for getting image with invalid ID") + assertWithMessage("Get image exception") .that(thrown).hasMessageThat().contains("Image ID is missing"); } @@ -430,6 +457,138 @@ public final class TunerSessionHidlTest { verify(mHalTunerSessionMock).stopProgramListUpdates(); } + @Test + public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG; + + boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag); + + verify(mHalTunerSessionMock).isConfigFlagSet(eq(flag), any()); + assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse(); + } + + @Test + public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG + 1; + + boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag); + + verify(mHalTunerSessionMock).isConfigFlagSet(eq(flag), any()); + assertWithMessage("Config flag %s is supported", flag).that(isSupported).isTrue(); + } + + @Test + public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG; + + RuntimeException thrown = assertThrows(RuntimeException.class, () -> { + mTunerSessions[0].setConfigFlag(flag, /* value= */ true); + }); + + assertWithMessage("Exception for setting unsupported flag %s", flag) + .that(thrown).hasMessageThat().contains("setConfigFlag: NOT_SUPPORTED"); + } + + @Test + public void setConfigFlag_withFlagSetToTrue() throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG + 1; + + mTunerSessions[0].setConfigFlag(flag, /* value= */ true); + + verify(mHalTunerSessionMock).setConfigFlag(flag, /* value= */ true); + } + + @Test + public void setConfigFlag_withFlagSetToFalse() throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG + 1; + + mTunerSessions[0].setConfigFlag(flag, /* value= */ false); + + verify(mHalTunerSessionMock).setConfigFlag(flag, /* value= */ false); + } + + @Test + public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException() + throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG; + + RuntimeException thrown = assertThrows(RuntimeException.class, () -> { + mTunerSessions[0].isConfigFlagSet(flag); + }); + + assertWithMessage("Exception for check if unsupported flag %s is set", flag) + .that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED"); + } + + @Test + public void isConfigFlagSet_withSupportedFlag() throws Exception { + openAidlClients(/* numClients= */ 1); + int flag = UNSUPPORTED_CONFIG_FLAG + 1; + boolean expectedConfigFlagValue = true; + mTunerSessions[0].setConfigFlag(flag, /* value= */ expectedConfigFlagValue); + + boolean isSet = mTunerSessions[0].isConfigFlagSet(flag); + + assertWithMessage("Config flag %s is set", flag) + .that(isSet).isEqualTo(expectedConfigFlagValue); + } + + @Test + public void setParameters_withMockParameters() throws Exception { + openAidlClients(/* numClients= */ 1); + Map parametersSet = Map.of("mockParam1", "mockValue1", + "mockParam2", "mockValue2"); + + mTunerSessions[0].setParameters(parametersSet); + + verify(mHalTunerSessionMock).setParameters(Convert.vendorInfoToHal(parametersSet)); + } + + @Test + public void getParameters_withMockKeys() throws Exception { + openAidlClients(/* numClients= */ 1); + ArrayList parameterKeys = new ArrayList<>(Arrays.asList("mockKey1", "mockKey2")); + + mTunerSessions[0].getParameters(parameterKeys); + + verify(mHalTunerSessionMock).getParameters(parameterKeys); + } + + @Test + public void onConfigFlagUpdated_forTunerCallback() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + + mHalTunerCallback.onAntennaStateChange(/* connected= */ false); + + for (int index = 0; index < numSessions; index++) { + verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) + .onAntennaState(/* connected= */ false); + } + } + + @Test + public void onParametersUpdated_forTunerCallback() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + ArrayList parametersUpdates = new ArrayList(Arrays.asList( + TestUtils.makeVendorKeyValue("com.vendor.parameter1", "value1"))); + Map parametersExpected = Map.of("com.vendor.parameter1", "value1"); + + mHalTunerCallback.onParametersUpdated(parametersUpdates); + + for (int index = 0; index < numSessions; index++) { + verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) + .onParametersUpdated(parametersExpected); + } + } + private void openAidlClients(int numClients) throws Exception { mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; mTunerSessions = new TunerSession[numClients]; diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 560573799a3da..2178a21ff7451 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -135,7 +135,7 @@ public class BroadcastRadioService { public @NonNull Collection listModules() { Slog.v(TAG, "List HIDL 2.0 modules"); synchronized (mLock) { - return mModules.values().stream().map(module -> module.mProperties) + return mModules.values().stream().map(module -> module.getProperties()) .collect(Collectors.toList()); } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 0a23e385d67a6..5913e0685eb4f 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -58,7 +58,7 @@ class RadioModule { private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25; @NonNull private final IBroadcastRadio mService; - @NonNull public final RadioManager.ModuleProperties mProperties; + @NonNull private final RadioManager.ModuleProperties mProperties; private final Object mLock; @NonNull private final Handler mHandler; @@ -177,6 +177,10 @@ class RadioModule { return mService; } + public RadioManager.ModuleProperties getProperties() { + return mProperties; + } + public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) throws RemoteException { mEventLogger.logRadioEvent("Open TunerSession"); From c58f5806700d1640c2c727b5b8b73a1377f2c22f Mon Sep 17 00:00:00 2001 From: Weilin Xu Date: Wed, 9 Nov 2022 08:35:53 +0000 Subject: [PATCH 2/2] Add unit tests for HIDL broadcast radio service Bug: 258034192 Test: atest com.android.server.broadcastradio Change-Id: I76cd338d20be7fb5effb137ff9f0601927105056 --- .../aidl/BroadcastRadioServiceImplTest.java | 4 +- .../hal2/BroadcastRadioServiceHidlTest.java | 220 ++++++++++++++++++ .../hal2/BroadcastRadioService.java | 15 +- 3 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java index d061a778e369d..635d1e7927150 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java @@ -122,7 +122,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes createBroadcastRadioService(); ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, - /*legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Session opened in FM radio module") .that(session).isEqualTo(mFmTunerSessionMock); @@ -133,7 +133,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes createBroadcastRadioService(); ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1, - /*legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Session opened with id not found").that(session).isNull(); } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java new file mode 100644 index 0000000000000..4d0b753b0acc1 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2022 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.server.broadcastradio.hal2; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.broadcastradio.V2_0.IBroadcastRadio; +import android.hardware.radio.Announcement; +import android.hardware.radio.IAnnouncementListener; +import android.hardware.radio.ICloseHandle; +import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; +import android.hardware.radio.RadioManager; +import android.hardware.radio.RadioTuner; +import android.hidl.manager.V1_0.IServiceManager; +import android.hidl.manager.V1_0.IServiceNotification; +import android.os.IBinder; +import android.os.IHwBinder.DeathRecipient; +import android.os.RemoteException; + +import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; +import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; + +import org.junit.Test; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.Arrays; + +public final class BroadcastRadioServiceHidlTest extends ExtendedRadioMockitoTestCase { + + private static final int FM_RADIO_MODULE_ID = 0; + private static final int DAB_RADIO_MODULE_ID = 1; + private static final ArrayList SERVICE_LIST = + new ArrayList<>(Arrays.asList("FmService", "DabService")); + private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; + + private final Object mLock = new Object(); + + private BroadcastRadioService mBroadcastRadioService; + private DeathRecipient mFmDeathRecipient; + + @Mock + private IServiceManager mServiceManagerMock; + @Mock + private RadioManager.ModuleProperties mFmModuleMock; + @Mock + private RadioManager.ModuleProperties mDabModuleMock; + @Mock + private RadioModule mFmRadioModuleMock; + @Mock + private RadioModule mDabRadioModuleMock; + @Mock + private IBroadcastRadio mFmHalServiceMock; + @Mock + private IBroadcastRadio mDabHalServiceMock; + @Mock + private TunerSession mFmTunerSessionMock; + @Mock + private ITunerCallback mTunerCallbackMock; + @Mock + private ICloseHandle mFmCloseHandleMock; + @Mock + private ICloseHandle mDabCloseHandleMock; + @Mock + private IAnnouncementListener mAnnouncementListenerMock; + @Mock + private IBinder mBinderMock; + + @Override + protected void initializeSession(StaticMockitoSessionBuilder builder) { + builder.spyStatic(RadioModule.class); + } + + @Test + public void listModules_withMultipleServiceNames() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("Radio modules in HIDL broadcast radio HAL client") + .that(mBroadcastRadioService.listModules()) + .containsExactly(mFmModuleMock, mDabModuleMock); + } + + @Test + public void hasModules_withIdFoundInModules() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("DAB radio module in HIDL broadcast radio HAL client") + .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isTrue(); + } + + @Test + public void hasModules_withIdNotFoundInModules() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("Radio module of id not found in HIDL broadcast radio HAL client") + .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID + 1)).isFalse(); + } + + @Test + public void hasAnyModules_withModulesExist() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("Any radio module in HIDL broadcast radio HAL client") + .that(mBroadcastRadioService.hasAnyModules()).isTrue(); + } + + @Test + public void openSession_withIdFound() throws Exception { + createBroadcastRadioService(); + + ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); + + assertWithMessage("Session opened in FM radio module") + .that(session).isEqualTo(mFmTunerSessionMock); + } + + @Test + public void openSession_withIdNotFound() throws Exception { + createBroadcastRadioService(); + int moduleIdInvalid = DAB_RADIO_MODULE_ID + 1; + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + mBroadcastRadioService.openSession(moduleIdInvalid, /* legacyConfig= */ null, + /* withAudio= */ true, mTunerCallbackMock); + }); + + assertWithMessage("Exception for opening session with module id %s", moduleIdInvalid) + .that(thrown).hasMessageThat().contains("Invalid module ID"); + } + + @Test + public void addAnnouncementListener_addsOnAllRadioModules() throws Exception { + createBroadcastRadioService(); + when(mAnnouncementListenerMock.asBinder()).thenReturn(mBinderMock); + when(mFmRadioModuleMock.addAnnouncementListener(any(), any())) + .thenReturn(mFmCloseHandleMock); + when(mDabRadioModuleMock.addAnnouncementListener(any(), any())) + .thenReturn(mDabCloseHandleMock); + + mBroadcastRadioService.addAnnouncementListener(TEST_ENABLED_TYPES, + mAnnouncementListenerMock); + + verify(mFmRadioModuleMock).addAnnouncementListener(any(), any()); + verify(mDabRadioModuleMock).addAnnouncementListener(any(), any()); + } + + @Test + public void binderDied_forDeathRecipient() throws Exception { + createBroadcastRadioService(); + + mFmDeathRecipient.serviceDied(FM_RADIO_MODULE_ID); + + verify(mFmRadioModuleMock).closeSessions(eq(RadioTuner.ERROR_HARDWARE_FAILURE)); + assertWithMessage("FM radio module after FM broadcast radio HAL service died") + .that(mBroadcastRadioService.hasModule(FM_RADIO_MODULE_ID)).isFalse(); + } + + private void createBroadcastRadioService() throws RemoteException { + mockServiceManager(); + mBroadcastRadioService = new BroadcastRadioService(/* nextModuleId= */ FM_RADIO_MODULE_ID, + mLock, mServiceManagerMock); + } + + private void mockServiceManager() throws RemoteException { + doAnswer(invocation -> { + mFmDeathRecipient = (DeathRecipient) invocation.getArguments()[0]; + return null; + }).when(mFmHalServiceMock).linkToDeath(any(), eq((long) FM_RADIO_MODULE_ID)); + + when(mServiceManagerMock.registerForNotifications(anyString(), anyString(), + any(IServiceNotification.class))).thenAnswer(invocation -> { + IServiceNotification serviceCallback = + (IServiceNotification) invocation.getArguments()[2]; + for (int index = 0; index < SERVICE_LIST.size(); index++) { + serviceCallback.onRegistration(IBroadcastRadio.kInterfaceName, + SERVICE_LIST.get(index), /* b= */ false); + } + return true; + }).thenReturn(true); + + doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule( + eq(FM_RADIO_MODULE_ID), anyString(), any(Object.class))); + doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule( + eq(DAB_RADIO_MODULE_ID), anyString(), any(Object.class))); + + when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock); + when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock); + + when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock); + when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock); + + when(mFmRadioModuleMock.openSession(mTunerCallbackMock)) + .thenReturn(mFmTunerSessionMock); + } +} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 2178a21ff7451..4c3760928df32 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -33,6 +33,7 @@ import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.Collection; import java.util.HashMap; @@ -132,6 +133,18 @@ public class BroadcastRadioService { } } + @VisibleForTesting + BroadcastRadioService(int nextModuleId, Object lock, IServiceManager manager) { + mNextModuleId = nextModuleId; + mLock = lock; + Objects.requireNonNull(manager, "Service manager cannot be null"); + try { + manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to register for service notifications: ", ex); + } + } + public @NonNull Collection listModules() { Slog.v(TAG, "List HIDL 2.0 modules"); synchronized (mLock) { @@ -154,7 +167,7 @@ public class BroadcastRadioService { public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException { - Slog.v(TAG, "Open HIDL 2.0 session"); + Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId); Objects.requireNonNull(callback); if (!withAudio) {