diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java new file mode 100644 index 0000000000000..fe3ab625bacc7 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java @@ -0,0 +1,433 @@ +/* + * 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 android.hardware.radio.tests.unittests; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.graphics.Bitmap; +import android.hardware.radio.IRadioService; +import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; +import android.hardware.radio.ProgramSelector; +import android.hardware.radio.RadioManager; +import android.hardware.radio.RadioMetadata; +import android.hardware.radio.RadioTuner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@RunWith(MockitoJUnitRunner.class) +public final class TunerAdapterTest { + + private static final int CALLBACK_TIMEOUT_MS = 30_000; + private static final int AM_LOWER_LIMIT_KHZ = 150; + + private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig(); + + private static final ProgramSelector.Identifier FM_IDENTIFIER = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, + /* value= */ 94300); + private static final ProgramSelector FM_SELECTOR = + new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER, + /* secondaryIds= */ null, /* vendorIds= */ null); + private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo(); + + private RadioTuner mRadioTuner; + private ITunerCallback mTunerCallback; + + @Mock + private IRadioService mRadioServiceMock; + @Mock + private Context mContextMock; + @Mock + private ITuner mTunerMock; + @Mock + private RadioTuner.Callback mCallbackMock; + + @Before + public void setUp() throws Exception { + RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock); + + doAnswer(invocation -> { + mTunerCallback = (ITunerCallback) invocation.getArguments()[3]; + return mTunerMock; + }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any()); + + doAnswer(invocation -> { + ProgramSelector program = (ProgramSelector) invocation.getArguments()[0]; + if (program.getPrimaryId().getType() + != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) { + throw new IllegalArgumentException(); + } + if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) { + mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program); + } else { + mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO); + } + return RadioManager.STATUS_OK; + }).when(mTunerMock).tune(any()); + + mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, TEST_BAND_CONFIG, + /* withAudio= */ true, mCallbackMock, /* handler= */ null); + } + + @After + public void cleanUp() throws Exception { + mRadioTuner.close(); + } + + @Test + public void close_forTunerAdapter() throws Exception { + mRadioTuner.close(); + + verify(mTunerMock).close(); + } + + @Test + public void setConfiguration_forTunerAdapter() throws Exception { + int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG); + + verify(mTunerMock).setConfiguration(TEST_BAND_CONFIG); + assertWithMessage("Status for setting configuration") + .that(status).isEqualTo(RadioManager.STATUS_OK); + } + + @Test + public void getConfiguration_forTunerAdapter() throws Exception { + when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG); + RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1]; + + int status = mRadioTuner.getConfiguration(bandConfigs); + + assertWithMessage("Status for getting configuration") + .that(status).isEqualTo(RadioManager.STATUS_OK); + assertWithMessage("Configuration obtained from radio tuner") + .that(bandConfigs[0]).isEqualTo(TEST_BAND_CONFIG); + } + + @Test + public void setMute_forTunerAdapter() { + int status = mRadioTuner.setMute(/* mute= */ true); + + assertWithMessage("Status for setting mute") + .that(status).isEqualTo(RadioManager.STATUS_OK); + } + + @Test + public void getMute_forTunerAdapter() throws Exception { + when(mTunerMock.isMuted()).thenReturn(true); + + boolean muteStatus = mRadioTuner.getMute(); + + assertWithMessage("Mute status").that(muteStatus).isTrue(); + } + + @Test + public void step_forTunerAdapter_succeeds() throws Exception { + doAnswer(invocation -> { + mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO); + return RadioManager.STATUS_OK; + }).when(mTunerMock).step(anyBoolean(), anyBoolean()); + + int scanStatus = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false); + + verify(mTunerMock).step(/* skipSubChannel= */ true, /* skipSubChannel= */ false); + assertWithMessage("Status for stepping") + .that(scanStatus).isEqualTo(RadioManager.STATUS_OK); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); + } + + @Test + public void seek_forTunerAdapter_succeeds() throws Exception { + doAnswer(invocation -> { + mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO); + return RadioManager.STATUS_OK; + }).when(mTunerMock).scan(anyBoolean(), anyBoolean()); + + int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false); + + verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false); + assertWithMessage("Status for seeking") + .that(scanStatus).isEqualTo(RadioManager.STATUS_OK); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); + } + + @Test + public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception { + doAnswer(invocation -> { + mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT); + return RadioManager.STATUS_OK; + }).when(mTunerMock).scan(anyBoolean(), anyBoolean()); + + mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT); + } + + @Test + public void tune_withChannelsForTunerAdapter_succeeds() { + int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0); + + assertWithMessage("Status for tuning with channel and sub-channel") + .that(status).isEqualTo(RadioManager.STATUS_OK); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); + } + + @Test + public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception { + mRadioTuner.tune(FM_SELECTOR); + + verify(mTunerMock).tune(FM_SELECTOR); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); + } + + + @Test + public void tune_withInvalidSelectorForTunerAdapter_invokesOnTuneFailed() { + ProgramSelector invalidSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, + new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 100), + /* secondaryIds= */ null, /* vendorIds= */ null); + + mRadioTuner.tune(invalidSelector); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) + .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector); + } + + @Test + public void cancel_forTunerAdapter() throws Exception { + mRadioTuner.tune(FM_SELECTOR); + + mRadioTuner.cancel(); + + verify(mTunerMock).cancel(); + } + + @Test + public void cancelAnnouncement_forTunerAdapter() throws Exception { + mRadioTuner.cancelAnnouncement(); + + verify(mTunerMock).cancelAnnouncement(); + } + + @Test + public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() { + RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1]; + + int status = mRadioTuner.getProgramInformation(programInfoArray); + + assertWithMessage("Status for getting null program info") + .that(status).isEqualTo(RadioManager.STATUS_INVALID_OPERATION); + } + + @Test + public void getProgramInfo_afterTuneForTunerAdapter() { + mRadioTuner.tune(FM_SELECTOR); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); + RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1]; + + int status = mRadioTuner.getProgramInformation(programInfoArray); + + assertWithMessage("Status for getting program info") + .that(status).isEqualTo(RadioManager.STATUS_OK); + assertWithMessage("Program info obtained from radio tuner") + .that(programInfoArray[0]).isEqualTo(FM_PROGRAM_INFO); + } + + @Test + public void getMetadataImage_forTunerAdapter() throws Exception { + Bitmap bitmapExpected = Mockito.mock(Bitmap.class); + when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected); + int imageId = 1; + + Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId); + + assertWithMessage("Image obtained from id %s", imageId) + .that(image).isEqualTo(bitmapExpected); + } + + @Test + public void isAnalogForced_forTunerAdapter() throws Exception { + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true); + + boolean isAnalogForced = mRadioTuner.isAnalogForced(); + + assertWithMessage("Forced analog playback switch") + .that(isAnalogForced).isTrue(); + } + + @Test + public void setAnalogForced_forTunerAdapter() throws Exception { + boolean analogForced = true; + + mRadioTuner.setAnalogForced(analogForced); + + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, analogForced); + } + + @Test + public void isConfigFlagSupported_forTunerAdapter() throws Exception { + when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING)) + .thenReturn(true); + + boolean dabFmSoftLinking = + mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING); + + assertWithMessage("Support for DAB-DAB linking config flag") + .that(dabFmSoftLinking).isTrue(); + } + + @Test + public void isConfigFlagSet_forTunerAdapter() throws Exception { + when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING)) + .thenReturn(true); + + boolean dabFmSoftLinking = + mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING); + + assertWithMessage("DAB-FM soft linking config flag") + .that(dabFmSoftLinking).isTrue(); + } + + @Test + public void setConfigFlag_forTunerAdapter() throws Exception { + boolean dabFmLinking = true; + + mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking); + + verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking); + } + + @Test + public void getParameters_forTunerAdapter() throws Exception { + List parameterKeys = Arrays.asList("ParameterKeyMock"); + Map parameters = Map.of("ParameterKeyMock", "ParameterValueMock"); + when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters); + + assertWithMessage("Parameters obtained from radio tuner") + .that(mRadioTuner.getParameters(parameterKeys)).isEqualTo(parameters); + } + + @Test + public void setParameters_forTunerAdapter() throws Exception { + Map parameters = Map.of("ParameterKeyMock", "ParameterValueMock"); + when(mTunerMock.setParameters(parameters)).thenReturn(parameters); + + assertWithMessage("Parameters set for radio tuner") + .that(mRadioTuner.setParameters(parameters)).isEqualTo(parameters); + } + + @Test + public void isAntennaConnected_forTunerAdapter() throws Exception { + mTunerCallback.onAntennaState(/* connected= */ false); + + assertWithMessage("Antenna connection status") + .that(mRadioTuner.isAntennaConnected()).isFalse(); + } + + @Test + public void hasControl_forTunerAdapter() throws Exception { + when(mTunerMock.isClosed()).thenReturn(true); + + assertWithMessage("Control on tuner").that(mRadioTuner.hasControl()).isFalse(); + } + + @Test + public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception { + mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) + .onConfigurationChanged(TEST_BAND_CONFIG); + } + + @Test + public void onTrafficAnnouncement_forTunerCallbackAdapter() throws Exception { + mTunerCallback.onTrafficAnnouncement(/* active= */ true); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) + .onTrafficAnnouncement(/* active= */ true); + } + + @Test + public void onEmergencyAnnouncement_forTunerCallbackAdapter() throws Exception { + mTunerCallback.onEmergencyAnnouncement(/* active= */ true); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) + .onEmergencyAnnouncement(/* active= */ true); + } + + @Test + public void onBackgroundScanAvailabilityChange_forTunerCallbackAdapter() throws Exception { + mTunerCallback.onBackgroundScanAvailabilityChange(/* isAvailable= */ false); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) + .onBackgroundScanAvailabilityChange(/* isAvailable= */ false); + } + + @Test + public void onProgramListChanged_forTunerCallbackAdapter() throws Exception { + mTunerCallback.onProgramListChanged(); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramListChanged(); + } + + @Test + public void onParametersUpdated_forTunerCallbackAdapter() throws Exception { + Map parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock"); + + mTunerCallback.onParametersUpdated(parametersExpected); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onParametersUpdated(parametersExpected); + } + + private static RadioManager.ProgramInfo createFmProgramInfo() { + return new RadioManager.ProgramInfo(FM_SELECTOR, FM_IDENTIFIER, FM_IDENTIFIER, + /* relatedContent= */ null, /* infoFlags= */ 0b110001, + /* signalQuality= */ 1, createRadioMetadata(), /* vendorInfo= */ null); + } + + private static RadioManager.FmBandConfig createBandConfig() { + return new RadioManager.FmBandConfig(new RadioManager.FmBandDescriptor( + RadioManager.REGION_ITU_1, RadioManager.BAND_FM, /* lowerLimit= */ 87500, + /* upperLimit= */ 108000, /* spacing= */ 200, /* stereo= */ true, + /* rds= */ false, /* ta= */ false, /* af= */ false, /* es= */ false)); + } + + private static RadioMetadata createRadioMetadata() { + RadioMetadata.Builder metadataBuilder = new RadioMetadata.Builder(); + return metadataBuilder.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistMock").build(); + } +}