Add unit tests for radio program list

Bug: 249602599
Test: atest android.hardware.radio.tests.unittests
Change-Id: Ia52d322ffc365be54096d3220a6b05f3fecbc207
This commit is contained in:
Weilin Xu
2022-11-02 21:40:30 +00:00
parent bf06bafa98
commit 1810a320c6
2 changed files with 504 additions and 0 deletions

View File

@@ -0,0 +1,494 @@
/*
* 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.junit.Assert.assertThrows;
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.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.hardware.radio.IRadioService;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArraySet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.verification.VerificationWithTimeout;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
@RunWith(MockitoJUnitRunner.class)
public final class ProgramListTest {
private static final int CREATOR_ARRAY_SIZE = 3;
private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
private static final boolean IS_PURGE = false;
private static final boolean IS_COMPLETE = true;
private static final boolean INCLUDE_CATEGORIES = true;
private static final boolean EXCLUDE_MODIFICATIONS = false;
private static final ProgramSelector.Identifier FM_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
/* value= */ 94300);
private static final ProgramSelector.Identifier RDS_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
/* value= */ 0x10000111);
private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
/* value= */ 0x1013);
private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo(
createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER));
private static final RadioManager.ProgramInfo RDS_PROGRAM_INFO = createFmProgramInfo(
createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, RDS_IDENTIFIER));
private static final Set<Integer> FILTER_IDENTIFIER_TYPES = Set.of(
ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
private static final Set<ProgramSelector.Identifier> FILTER_IDENTIFIERS = Set.of(FM_IDENTIFIER);
private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
/* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
private static final Map<String, String> VENDOR_FILTER = Map.of("testVendorKey1",
"testVendorValue1", "testVendorKey2", "testVendorValue2");
private final Executor mExecutor = new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
};
private RadioTuner mRadioTuner;
private ITunerCallback mTunerCallback;
private ProgramList mProgramList;
private ProgramList.ListCallback[] mListCallbackMocks;
private ProgramList.OnCompleteListener[] mOnCompleteListenerMocks;
@Mock
private IRadioService mRadioServiceMock;
@Mock
private Context mContextMock;
@Mock
private ITuner mTunerMock;
@Mock
private RadioTuner.Callback mTunerCallbackMock;
@Test
public void getIdentifierTypes_forFilter() {
ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes())
.containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES);
}
@Test
public void getIdentifiers_forFilter() {
ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
assertWithMessage("Filtered identifiers").that(filter.getIdentifiers())
.containsExactlyElementsIn(FILTER_IDENTIFIERS);
}
@Test
public void areCategoriesIncluded_forFilter() {
ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
assertWithMessage("Filter including categories")
.that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES);
}
@Test
public void areModificationsExcluded_forFilter() {
ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
assertWithMessage("Filter excluding modifications")
.that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS);
}
@Test
public void getVendorFilter_forFilterWithoutVendorFilter_returnsNull() {
ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
assertWithMessage("Filter vendor obtained from filter without vendor filter")
.that(filter.getVendorFilter()).isNull();
}
@Test
public void getVendorFilter_forFilterWithVendorFilter() {
ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER);
assertWithMessage("Filter vendor obtained from filter with vendor filter")
.that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER);
}
@Test
public void describeContents_forFilter() {
assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
}
@Test
public void hashCode_withTheSameFilters_equals() {
ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
assertWithMessage("Hash code of the same filter")
.that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode());
}
@Test
public void hashCode_withDifferentFilters_notEquals() {
ProgramList.Filter filterCompared = new ProgramList.Filter();
assertWithMessage("Hash code of the different filter")
.that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode());
}
@Test
public void writeToParcel_forFilter() {
Parcel parcel = Parcel.obtain();
TEST_FILTER.writeToParcel(parcel, /* flags= */ 0);
parcel.setDataPosition(0);
ProgramList.Filter filterFromParcel =
ProgramList.Filter.CREATOR.createFromParcel(parcel);
assertWithMessage("Filter created from parcel")
.that(filterFromParcel).isEqualTo(TEST_FILTER);
}
@Test
public void newArray_forFilterCreator() {
ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE);
assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
}
@Test
public void isPurge_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
}
@Test
public void isComplete_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
}
@Test
public void getModified_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Modified program info in chunk")
.that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
}
@Test
public void getRemoved_forChunk() {
ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
.containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
}
@Test
public void describeContents_forChunk() {
assertWithMessage("Chunk contents").that(FM_RDS_ADD_CHUNK.describeContents()).isEqualTo(0);
}
@Test
public void writeToParcel_forChunk() {
Parcel parcel = Parcel.obtain();
FM_RDS_ADD_CHUNK.writeToParcel(parcel, /* flags= */ 0);
parcel.setDataPosition(0);
ProgramList.Chunk chunkFromParcel =
ProgramList.Chunk.CREATOR.createFromParcel(parcel);
assertWithMessage("Chunk created from parcel")
.that(chunkFromParcel).isEqualTo(FM_RDS_ADD_CHUNK);
}
@Test
public void newArray_forChunkCreator() {
ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE);
assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
}
@Test
public void getDynamicProgramList_forTunerAdapter() throws Exception {
createRadioTuner();
mRadioTuner.getDynamicProgramList(TEST_FILTER);
verify(mTunerMock).startProgramListUpdates(TEST_FILTER);
}
@Test
public void getDynamicProgramList_forTunerAdapterWithServiceDied_throwsException()
throws Exception {
createRadioTuner();
doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
mRadioTuner.getDynamicProgramList(TEST_FILTER);
});
assertWithMessage("Exception for radio HAL client service died")
.that(thrown).hasMessageThat().contains("Service died");
}
@Test
public void onProgramListUpdated_withNewIdsAdded_invokesMockedCallbacks() throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
addOnCompleteListeners(/* numListeners= */ 1);
mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(RDS_IDENTIFIER);
verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
assertWithMessage("Program info in program list after adding FM and RDS info")
.that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
}
@Test
public void onProgramListUpdated_withIdsRemoved_invokesMockedCallbacks() throws Exception {
ProgramList.Chunk fmRemovedChunk = new ProgramList.Chunk(/* purge= */ false,
/* complete= */ false, new ArraySet<>(), Set.of(FM_IDENTIFIER));
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
mTunerCallback.onProgramListUpdated(fmRemovedChunk);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
assertWithMessage("Program info in program list after removing FM id")
.that(mProgramList.toList()).containsExactly(RDS_PROGRAM_INFO);
assertWithMessage("Program info FM identifier")
.that(mProgramList.get(RDS_IDENTIFIER)).isEqualTo(RDS_PROGRAM_INFO);
}
@Test
public void onProgramListUpdated_withIncompleteChunk_notInvokesOnCompleteListener()
throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
addOnCompleteListeners(/* numListeners= */ 1);
mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
}
@Test
public void onProgramListUpdated_withPurgeChunk() throws Exception {
ProgramList.Chunk purgeChunk = new ProgramList.Chunk(/* purge= */ true,
/* complete= */ true, new ArraySet<>(), new ArraySet<>());
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
mTunerCallback.onProgramListUpdated(purgeChunk);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(RDS_IDENTIFIER);
assertWithMessage("Program list after purge chunk applied")
.that(mProgramList.toList()).isEmpty();
}
@Test
public void registerListCallback_withExecutor() throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
ProgramList.ListCallback listCallbackMock = mock(ProgramList.ListCallback.class);
mProgramList.registerListCallback(mExecutor, listCallbackMock);
mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
verify(listCallbackMock, CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
}
@Test
public void onProgramListUpdated_withMultipleListCallBacks() throws Exception {
int numCallbacks = 3;
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(numCallbacks);
mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
for (int index = 0; index < numCallbacks; index++) {
verify(mListCallbackMocks[index], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
}
}
@Test
public void unregisterListCallback_withProgramUpdated_notInvokesCallback() throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
registerListCallbacks(/* numCallbacks= */ 1);
mProgramList.unregisterListCallback(mListCallbackMocks[0]);
mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
verify(mListCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onItemChanged(any());
}
@Test
public void addOnCompleteListener_withExecutor() throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
ProgramList.OnCompleteListener onCompleteListenerMock =
mock(ProgramList.OnCompleteListener.class);
mProgramList.addOnCompleteListener(mExecutor, onCompleteListenerMock);
mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
verify(onCompleteListenerMock, CALLBACK_TIMEOUT).onComplete();
}
@Test
public void onProgramListUpdated_withMultipleOnCompleteListeners() throws Exception {
int numListeners = 3;
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
addOnCompleteListeners(numListeners);
mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
for (int index = 0; index < numListeners; index++) {
verify(mOnCompleteListenerMocks[index], CALLBACK_TIMEOUT).onComplete();
}
}
@Test
public void removeOnCompleteListener_withProgramUpdated_notInvokesListener() throws Exception {
createRadioTuner();
mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
addOnCompleteListeners(/* numListeners= */ 1);
mProgramList.removeOnCompleteListener(mOnCompleteListenerMocks[0]);
mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
}
@Test
public void close_forProgramList_invokesStopProgramListUpdates() throws Exception {
createRadioTuner();
ProgramList programList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
programList.close();
verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates();
}
private static ProgramSelector createProgramSelector(int programType,
ProgramSelector.Identifier identifier) {
return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
/* vendorIds= */ null);
}
private static RadioManager.ProgramInfo createFmProgramInfo(ProgramSelector selector) {
return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
selector.getPrimaryId(), /* relatedContents= */ null, /* infoFlags= */ 0,
/* signalQuality= */ 1, new RadioMetadata.Builder().build(),
/* vendorInfo= */ null);
}
private void createRadioTuner() throws Exception {
RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
RadioManager.BandConfig band = 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));
doAnswer(invocation -> {
mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
return mTunerMock;
}).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
/* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
}
private void registerListCallbacks(int numCallbacks) {
mListCallbackMocks = new ProgramList.ListCallback[numCallbacks];
for (int index = 0; index < numCallbacks; index++) {
mListCallbackMocks[index] = mock(ProgramList.ListCallback.class);
mProgramList.registerListCallback(mListCallbackMocks[index]);
}
}
private void addOnCompleteListeners(int numListeners) {
mOnCompleteListenerMocks = new ProgramList.OnCompleteListener[numListeners];
for (int index = 0; index < numListeners; index++) {
mOnCompleteListenerMocks[index] = mock(ProgramList.OnCompleteListener.class);
mProgramList.addOnCompleteListener(mOnCompleteListenerMocks[index]);
}
}
}

View File

@@ -279,6 +279,16 @@ public final class TunerAdapterTest {
.that(image).isEqualTo(bitmapExpected);
}
@Test
public void startBackgroundScan_forTunerAdapter() throws Exception {
when(mTunerMock.startBackgroundScan()).thenReturn(false);
boolean scanStatus = mRadioTuner.startBackgroundScan();
verify(mTunerMock).startBackgroundScan();
assertWithMessage("Status for starting background scan").that(scanStatus).isFalse();
}
@Test
public void isAnalogForced_forTunerAdapter() throws Exception {
when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);