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);