Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d742ae9 authored by Weilin Xu's avatar Weilin Xu Committed by Android (Google) Code Review
Browse files

Merge "Add unit tests for radio service implementations"

parents 2c1b8c4d f05be095
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ android_test {
    ],
    // mockito-target-inline dependency
    jni_libs: [
        "libcarservicejni",
        "libdexmakerjvmtiagent",
        "libstaticjvmtiagent",
    ],
}
+32 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<configuration description="Runs Broadcast Radio Tests.">
    <option name="test-suite-tag" value="apct" />
    <option name="test-suite-tag" value="apct-instrumentation" />

    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
        <option name="cleanup-apks" value="true" />
        <option name="test-file-name" value="BroadcastRadioTests.apk" />
    </target_preparer>

    <option name="test-tag" value="BroadcastRadioTests" />

    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
        <option name="package" value="android.hardware.radio.tests" />
        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
        <option name="hidden-api-checks" value="false"/>
    </test>
</configuration>
+78 −0
Original line number Diff line number Diff line
/*
 * 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.
     *
     * <p>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();
        }
    }
}
+31 −9
Original line number Diff line number Diff line
@@ -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);
    }

}
+189 −0
Original line number Diff line number Diff line
/*
 * 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<String> 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<Void>) 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);
    }
}
Loading