diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp index 7cb64c8b4f0c3..436f058f3cbc5 100644 --- a/core/tests/BroadcastRadioTests/Android.bp +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -48,7 +48,7 @@ android_test { ], // mockito-target-inline dependency jni_libs: [ - "libcarservicejni", "libdexmakerjvmtiagent", + "libstaticjvmtiagent", ], } diff --git a/core/tests/BroadcastRadioTests/AndroidTest.xml b/core/tests/BroadcastRadioTests/AndroidTest.xml new file mode 100644 index 0000000000000..ed885376378c2 --- /dev/null +++ b/core/tests/BroadcastRadioTests/AndroidTest.xml @@ -0,0 +1,32 @@ + + + + diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java new file mode 100644 index 0000000000000..c6021ecce6b5c --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/ExtendedRadioMockitoTestCase.java @@ -0,0 +1,78 @@ +/* + * 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; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import android.util.Log; + +import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; + +import org.junit.After; +import org.junit.Before; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +/** + * Base class to make it easier to write tests that uses {@code ExtendedMockito} for radio. + * + */ +public abstract class ExtendedRadioMockitoTestCase { + + private static final String TAG = "RadioMockitoTestCase"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private MockitoSession mSession; + + @Before + public void startSession() { + StaticMockitoSessionBuilder builder = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT); + initializeSession(builder); + mSession = builder.startMocking(); + } + + /** + * Initializes the mockito session for radio test. + * + *

Typically used to define which classes should have static methods mocked or spied. + */ + protected void initializeSession(StaticMockitoSessionBuilder builder) { + if (DEBUG) { + Log.d(TAG, "initializeSession()"); + } + } + + @After + public final void finishSession() { + if (mSession == null) { + Log.w(TAG, "finishSession(): no session"); + return; + } + try { + if (DEBUG) { + Log.d(TAG, "finishSession()"); + } + } finally { + // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally + // statements), otherwise it would cause failures on future tests as mockito + // cannot start a session when a previous one is not finished + mSession.finishMocking(); + } + } +} 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 7f4ea1170084a..14dd5df1716fa 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java @@ -16,6 +16,8 @@ package com.android.server.broadcastradio; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -31,30 +33,36 @@ import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; +import android.os.IBinder; +import android.os.ServiceManager; +import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import com.android.server.broadcastradio.aidl.BroadcastRadioServiceImpl; 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.Arrays; +import java.util.List; /** * Tests for {@link android.hardware.radio.IRadioService} with AIDL HAL implementation */ -@RunWith(MockitoJUnitRunner.class) -public final class IRadioServiceAidlImplTest { +public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCase { private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; + private static final String AM_FM_SERVICE_NAME = + "android.hardware.broadcastradio.IBroadcastRadio/amfm"; + private static final String DAB_SERVICE_NAME = + "android.hardware.broadcastradio.IBroadcastRadio/dab"; private IRadioServiceAidlImpl mAidlImpl; @Mock private BroadcastRadioService mServiceMock; @Mock + private IBinder mServiceBinderMock; + @Mock private BroadcastRadioServiceImpl mHalMock; @Mock private RadioManager.ModuleProperties mModuleMock; @@ -73,7 +81,7 @@ public final class IRadioServiceAidlImplTest { public void setUp() throws Exception { doNothing().when(mServiceMock).enforcePolicyAccess(); - when(mHalMock.listModules()).thenReturn(Arrays.asList(mModuleMock)); + when(mHalMock.listModules()).thenReturn(List.of(mModuleMock)); when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any())) .thenReturn(mTunerMock); when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle); @@ -81,11 +89,26 @@ public final class IRadioServiceAidlImplTest { mAidlImpl = new IRadioServiceAidlImpl(mServiceMock, mHalMock); } + @Override + protected void initializeSession(StaticMockitoSessionBuilder builder) { + builder.spyStatic(ServiceManager.class); + } + + @Test + public void getServicesNames_forAidlImpl() { + doReturn(null).when(() -> ServiceManager.waitForDeclaredService( + AM_FM_SERVICE_NAME)); + doReturn(mServiceBinderMock).when(() -> ServiceManager.waitForDeclaredService( + DAB_SERVICE_NAME)); + + assertWithMessage("Names of services available") + .that(IRadioServiceAidlImpl.getServicesNames()).containsExactly(DAB_SERVICE_NAME); + } + @Test public void loadModules_forAidlImpl() { assertWithMessage("Modules loaded in AIDL HAL") - .that(mAidlImpl.listModules()) - .containsExactly(mModuleMock); + .that(mAidlImpl.listModules()).containsExactly(mModuleMock); } @Test @@ -105,5 +128,4 @@ public final class IRadioServiceAidlImplTest { assertWithMessage("Close handle of announcement listener for HAL 2") .that(closeHandle).isEqualTo(mICloseHandle); } - } 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 new file mode 100644 index 0000000000000..d061a778e369d --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java @@ -0,0 +1,189 @@ +/* + * 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.aidl; + +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.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +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.IBroadcastRadio; +import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; +import android.hardware.radio.RadioManager; +import android.hardware.radio.RadioTuner; +import android.os.IBinder; +import android.os.IServiceCallback; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; +import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; + +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Arrays; + +public final class BroadcastRadioServiceImplTest 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 BroadcastRadioServiceImpl mBroadcastRadioService; + private IBinder.DeathRecipient mFmDeathRecipient; + + @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 IBinder mFmBinderMock; + @Mock + private IBinder mDabBinderMock; + @Mock + private TunerSession mFmTunerSessionMock; + @Mock + private ITunerCallback mTunerCallbackMock; + + @Override + protected void initializeSession(StaticMockitoSessionBuilder builder) { + builder.spyStatic(ServiceManager.class) + .spyStatic(RadioModule.class); + } + + @Test + public void listModules_withMultipleServiceNames() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("Radio modules in AIDL broadcast radio HAL client") + .that(mBroadcastRadioService.listModules()) + .containsExactly(mFmModuleMock, mDabModuleMock); + } + + @Test + public void hasModules_withIdFoundInModules() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("DAB radio module in AIDL broadcast radio HAL client") + .that(mBroadcastRadioService.hasModule(DAB_RADIO_MODULE_ID)).isTrue(); + } + + @Test + public void hasModules_withIdNotFoundInModules() throws Exception { + createBroadcastRadioService(); + + assertWithMessage("Radio module of id not found in AIDL 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 AIDL 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(); + + ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1, + /*legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); + + assertWithMessage("Session opened with id not found").that(session).isNull(); + } + + @Test + public void binderDied_forDeathRecipient() throws Exception { + createBroadcastRadioService(); + + mFmDeathRecipient.binderDied(); + + 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 BroadcastRadioServiceImpl(SERVICE_LIST); + } + + private void mockServiceManager() throws RemoteException { + doAnswer((Answer) invocation -> { + String serviceName = (String) invocation.getArguments()[0]; + IServiceCallback serviceCallback = (IServiceCallback) invocation.getArguments()[1]; + IBinder mockBinder = serviceName.equals("FmService") ? mFmBinderMock : mDabBinderMock; + serviceCallback.onRegistration(serviceName, mockBinder); + return null; + }).when(() -> ServiceManager.registerForNotifications(anyString(), + any(IServiceCallback.class))); + + doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule( + eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class))); + doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule( + eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class))); + + when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock); + when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock); + + when(mFmRadioModuleMock.getService()).thenReturn(mFmHalServiceMock); + when(mDabRadioModuleMock.getService()).thenReturn(mDabHalServiceMock); + + when(mFmHalServiceMock.asBinder()).thenReturn(mFmBinderMock); + when(mDabHalServiceMock.asBinder()).thenReturn(mDabBinderMock); + + doAnswer(invocation -> { + mFmDeathRecipient = (IBinder.DeathRecipient) invocation.getArguments()[0]; + return null; + }).when(mFmBinderMock).linkToDeath(any(), anyInt()); + + when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock))) + .thenReturn(mFmTunerSessionMock); + } +} 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 cd1cd7e9439c2..c1a43367a13b1 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 @@ -47,6 +47,15 @@ import org.mockito.junit.MockitoJUnitRunner; 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); // Mocks @Mock @@ -63,14 +72,7 @@ public final class RadioModuleTest { @Before public void setup() throws RemoteException { - 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, - /* supportedProgramTypes= */ new int[]{}, - /* supportedIdentifierTypes */ new int[]{}, - /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock); + mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock); // TODO(b/241118988): test non-null image for getImage method when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null); @@ -87,6 +89,12 @@ public final class RadioModuleTest { .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 setInternalHalCallback_callbackSetInHal() throws Exception { mRadioModule.setInternalHalCallback(); diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java index 71ba2968baec5..4fcfea2d0e070 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java @@ -169,7 +169,7 @@ public final class BroadcastRadioServiceImpl { synchronized (mLock) { List moduleList = new ArrayList<>(mModules.size()); for (int i = 0; i < mModules.size(); i++) { - moduleList.add(mModules.valueAt(i).mProperties); + moduleList.add(mModules.valueAt(i).getProperties()); } return moduleList; } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java index c6dc431976308..d4c7242f71a92 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java @@ -54,11 +54,11 @@ final class RadioModule { private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25; private final IBroadcastRadio mService; - public final RadioManager.ModuleProperties mProperties; private final Object mLock; private final Handler mHandler; private final RadioLogger mLogger; + private final RadioManager.ModuleProperties mProperties; /** * Tracks antenna state reported by HAL (if any). @@ -217,6 +217,10 @@ final class RadioModule { return mService; } + public RadioManager.ModuleProperties getProperties() { + return mProperties; + } + void setInternalHalCallback() throws RemoteException { synchronized (mLock) { mService.setTunerCallback(mHalTunerCallback);