Add test for tuner session of HIDL broadcast radio

Bug: 258034192
Test: atest TunerSessionHidlTest
Change-Id: I095443b53b5335f0a2c73a4e542cc8da74fbaa5c
This commit is contained in:
Weilin Xu
2022-11-09 02:26:57 +00:00
parent 4d742ae972
commit aa823a5c73
3 changed files with 500 additions and 5 deletions

View File

@@ -210,7 +210,7 @@ public final class TunerSessionTest {
mTunerSessions[0].setMuted(/* mute= */ false);
assertWithMessage("Session mute state after setting muted %s", false)
assertWithMessage("Session mute state after setting unmuted")
.that(mTunerSessions[0].isMuted()).isFalse();
}
@@ -220,7 +220,7 @@ public final class TunerSessionTest {
mTunerSessions[0].setMuted(/* mute= */ true);
assertWithMessage("Session mute state after setting muted %s", true)
assertWithMessage("Session mute state after setting muted")
.that(mTunerSessions[0].isMuted()).isTrue();
}

View File

@@ -22,6 +22,7 @@ import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.HashMap;
@@ -32,14 +33,33 @@ final class TestUtils {
throw new UnsupportedOperationException("TestUtils class is noninstantiable");
}
static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
return new RadioManager.ProgramInfo(selector,
selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
/* infoFlags= */ 0, signalQuality,
new RadioMetadata.Builder().build(), new ArrayMap<>());
}
static RadioManager.ProgramInfo makeProgramInfo(int programType,
ProgramSelector.Identifier identifier, int signalQuality) {
// Note: If you set new fields, check if programInfoToHal() needs to be updated as well.
return new RadioManager.ProgramInfo(new ProgramSelector(programType, identifier, null,
null), null, null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
return new RadioManager.ProgramInfo(makeProgramSelector(programType, identifier), null,
null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
new HashMap<String, String>());
}
static ProgramSelector makeFmSelector(long freq) {
return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
freq));
}
static ProgramSelector makeProgramSelector(int programType,
ProgramSelector.Identifier identifier) {
return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
/* vendorIds= */ null);
}
static ProgramInfo programInfoToHal(RadioManager.ProgramInfo info) {
// Note that because Convert does not by design provide functions for all conversions, this
// function only copies fields that are set by makeProgramInfo().
@@ -57,10 +77,22 @@ final class TestUtils {
android.hardware.broadcastradio.V2_0.ProgramSelector halSelector =
new android.hardware.broadcastradio.V2_0.ProgramSelector();
halSelector.primaryId = halId;
halSelector.secondaryIds = new ArrayList<>();
halSelector.secondaryIds = new ArrayList<ProgramIdentifier>();
return halSelector;
}
static ProgramInfo makeHalProgramInfo(
android.hardware.broadcastradio.V2_0.ProgramSelector hwSel, int hwSignalQuality) {
ProgramInfo hwInfo = new ProgramInfo();
hwInfo.selector = hwSel;
hwInfo.logicallyTunedTo = hwSel.primaryId;
hwInfo.physicallyTunedTo = hwSel.primaryId;
hwInfo.signalQuality = hwSignalQuality;
hwInfo.relatedContent = new ArrayList<>();
hwInfo.metadata = new ArrayList<>();
return hwInfo;
}
static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
VendorKeyValue vendorKeyValue = new VendorKeyValue();
vendorKeyValue.key = vendorKey;

View File

@@ -0,0 +1,463 @@
/*
* 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.hal2;
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.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.V2_0.Constants;
import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
import android.hardware.broadcastradio.V2_0.ITunerCallback;
import android.hardware.broadcastradio.V2_0.ITunerSession;
import android.hardware.broadcastradio.V2_0.IdentifierType;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
import android.util.ArraySet;
import org.junit.Before;
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.ArrayList;
/**
* Tests for HIDL HAL TunerSession.
*/
@RunWith(MockitoJUnitRunner.class)
public final class TunerSessionHidlTest {
private static final VerificationWithTimeout CALLBACK_TIMEOUT =
timeout(/* millis= */ 200);
private static final int SIGNAL_QUALITY = 1;
private static final long AM_FM_FREQUENCY_SPACING = 500;
private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
/* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
/* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
/* ea= */ false);
private static final RadioManager.BandConfig FM_BAND_CONFIG =
new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
private static final int UNSUPPORTED_CONFIG_FLAG = 0;
private final Object mLock = new Object();
private RadioModule mRadioModule;
private ITunerCallback mHalTunerCallback;
private ProgramInfo mHalCurrentInfo;
private TunerSession[] mTunerSessions;
@Mock private IBroadcastRadio mBroadcastRadioMock;
@Mock ITunerSession mHalTunerSessionMock;
private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
@Before
public void setup() throws Exception {
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,
new int[] {}, new int[] {},
/* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
doAnswer(invocation -> {
mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback)
invocation.getArguments()[1];
cb.onValues(Result.OK, mHalTunerSessionMock);
return null;
}).when(mBroadcastRadioMock).openSession(any(), any());
doAnswer(invocation -> {
android.hardware.broadcastradio.V2_0.ProgramSelector halSel =
(android.hardware.broadcastradio.V2_0.ProgramSelector)
invocation.getArguments()[0];
mHalCurrentInfo = TestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY) {
return Result.NOT_SUPPORTED;
}
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
return Result.OK;
}).when(mHalTunerSessionMock).tune(any());
doAnswer(invocation -> {
if ((boolean) invocation.getArguments()[0]) {
mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
} else {
mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
}
mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
return Result.OK;
}).when(mHalTunerSessionMock).step(anyBoolean());
doAnswer(invocation -> {
if (mHalCurrentInfo == null) {
android.hardware.broadcastradio.V2_0.ProgramSelector placeHolderSelector =
TestUtils.makeHalFmSelector(/* freq= */ 97300);
mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
return Result.OK;
}
mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
mHalCurrentInfo.selector.primaryId.value,
!(boolean) invocation.getArguments()[0]);
mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
return Result.OK;
}).when(mHalTunerSessionMock).scan(anyBoolean(), anyBoolean());
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
}
@Test
public void openSession_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
for (int index = 0; index < numSessions; index++) {
assertWithMessage("Session of index %s close state", index)
.that(mTunerSessions[index].isClosed()).isFalse();
}
}
@Test
public void setConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
}
@Test
public void getConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
assertWithMessage("Session configuration").that(config)
.isEqualTo(FM_BAND_CONFIG);
}
@Test
public void setMuted_withUnmuted() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setMuted(/* mute= */ false);
assertWithMessage("Session mute state after setting unmuted")
.that(mTunerSessions[0].isMuted()).isFalse();
}
@Test
public void setMuted_withMuted() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setMuted(/* mute= */ true);
assertWithMessage("Session mute state after setting muted")
.that(mTunerSessions[0].isMuted()).isTrue();
}
@Test
public void close_withOneSession() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].close();
assertWithMessage("Close state of broadcast radio service session")
.that(mTunerSessions[0].isClosed()).isTrue();
}
@Test
public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
int closeIdx = 0;
mTunerSessions[closeIdx].close();
for (int index = 0; index < numSessions; index++) {
if (index == closeIdx) {
assertWithMessage(
"Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isTrue();
} else {
assertWithMessage(
"Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isFalse();
}
}
}
@Test
public void close_withOneSession_withError() throws Exception {
openAidlClients(/* numClients= */ 1);
int errorCode = RadioTuner.ERROR_SERVER_DIED;
mTunerSessions[0].close(errorCode);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
assertWithMessage("Close state of broadcast radio service session")
.that(mTunerSessions[0].isClosed()).isTrue();
}
@Test
public void closeSessions_withMultipleSessions_withError() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
int errorCode = RadioTuner.ERROR_SERVER_DIED;
mRadioModule.closeSessions(errorCode);
for (int index = 0; index < numSessions; index++) {
verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
assertWithMessage("Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isTrue();
}
}
@Test
public void tune_withOneSession() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
mTunerSessions[0].tune(initialSel);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
}
@Test
public void tune_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
mTunerSessions[0].tune(initialSel);
for (int index = 0; index < numSessions; index++) {
verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
.onCurrentProgramInfoChanged(tuneInfo);
}
}
@Test
public void tune_withUnsupportedSelector_throwsException() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
() -> mTunerSessions[0].tune(unsupportedSelector));
assertWithMessage("Exception for tuning on unsupported program selector")
.that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
}
@Test
public void step_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[1];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo stepUpInfo = TestUtils.makeProgramInfo(
TestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING), SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = TestUtils.makeHalProgramInfo(
Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onCurrentProgramInfoChanged(stepUpInfo);
}
@Test
public void step_withDirectionDown() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[1];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo stepDownInfo = TestUtils.makeProgramInfo(
TestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = TestUtils.makeHalProgramInfo(
Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onCurrentProgramInfoChanged(stepDownInfo);
}
@Test
public void scan_withDirectionUp() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = TestUtils.makeHalProgramInfo(
Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onCurrentProgramInfoChanged(scanUpInfo);
}
@Test
public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
int numSessions = 2;
openAidlClients(numSessions);
mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
for (int index = 0; index < numSessions; index++) {
verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
.onTuneFailed(eq(Result.TIMEOUT), any());
}
}
@Test
public void scan_withDirectionDown() throws Exception {
long initFreq = AM_FM_FREQUENCY_LIST[2];
ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
mHalCurrentInfo = TestUtils.makeHalProgramInfo(
Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
.onCurrentProgramInfoChanged(scanUpInfo);
}
@Test
public void cancel() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
mTunerSessions[0].tune(initialSel);
mTunerSessions[0].cancel();
verify(mHalTunerSessionMock).cancel();
}
@Test
public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = Constants.INVALID_IMAGE;
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
mTunerSessions[0].getImage(imageId);
});
assertWithMessage("Exception for getting image with invalid ID")
.that(thrown).hasMessageThat().contains("Image ID is missing");
}
@Test
public void getImage_withValidId() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = 1;
Bitmap imageTest = mTunerSessions[0].getImage(imageId);
assertWithMessage("Null image").that(imageTest).isEqualTo(null);
}
@Test
public void startBackgroundScan() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].startBackgroundScan();
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
}
@Test
public void stopProgramListUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
mTunerSessions[0].startProgramListUpdates(aidlFilter);
mTunerSessions[0].stopProgramListUpdates();
verify(mHalTunerSessionMock).stopProgramListUpdates();
}
private void openAidlClients(int numClients) throws Exception {
mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
mTunerSessions = new TunerSession[numClients];
for (int index = 0; index < numClients; index++) {
mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
}
}
private long getSeekFrequency(long currentFrequency, boolean seekDown) {
long seekFrequency;
if (seekDown) {
seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
seekFrequency = AM_FM_FREQUENCY_LIST[i];
break;
}
}
} else {
seekFrequency = AM_FM_FREQUENCY_LIST[0];
for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
seekFrequency = AM_FM_FREQUENCY_LIST[index];
break;
}
}
}
return seekFrequency;
}
}