From 9de25c7c70c34c09bc1abafb4ef2b7e3f43c4a4d Mon Sep 17 00:00:00 2001 From: Weilin Xu Date: Tue, 13 Sep 2022 01:10:48 +0000 Subject: [PATCH 1/2] Add test for AIDL radio client program list update Added unit tests for program list update using filter in AIDL broadcast radio HAL client. Also added unit tests for handing expcetion and non-current user cases during program list update. Bug: 241118988 Test: com.android.server.broadcastradio.aidl.ProgramInfoCacheTest Change-Id: I1ce5770207f11933c16dfb19c2c6b967b77d36fc --- .../broadcastradio/aidl/AidlTestUtils.java | 47 ++- .../aidl/ConversionUtilsTest.java | 4 +- .../broadcastradio/aidl/TunerSessionTest.java | 307 +++++++++++++++++- .../hal2/TunerSessionHidlTest.java | 34 +- 4 files changed, 376 insertions(+), 16 deletions(-) diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java index 82db716fcdc24..e2e9bd9695553 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java @@ -21,10 +21,15 @@ import android.hardware.broadcastradio.ProgramIdentifier; import android.hardware.broadcastradio.ProgramInfo; import android.hardware.broadcastradio.ProgramListChunk; import android.hardware.broadcastradio.VendorKeyValue; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; +import android.os.RemoteException; import android.util.ArrayMap; +import android.util.ArraySet; + +import java.util.List; final class AidlTestUtils { @@ -94,6 +99,14 @@ final class AidlTestUtils { return makeHalProgramInfo(hwSel, hwSel.primaryId, hwSel.primaryId, hwSignalQuality); } + static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) { + return makeHalProgramInfo( + ConversionUtils.programSelectorToHalProgramSelector(info.getSelector()), + ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo()), + ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo()), + info.getSignalStrength()); + } + static ProgramInfo makeHalProgramInfo( android.hardware.broadcastradio.ProgramSelector hwSel, ProgramIdentifier logicallyTunedTo, ProgramIdentifier physicallyTunedTo, @@ -108,7 +121,23 @@ final class AidlTestUtils { return hwInfo; } - static ProgramListChunk makeProgramListChunk(boolean purge, boolean complete, + static ProgramListChunk makeHalChunk(boolean purge, List modified, + List removed) { + ProgramInfo[] halModified = + new android.hardware.broadcastradio.ProgramInfo[modified.size()]; + for (int i = 0; i < modified.size(); i++) { + halModified[i] = programInfoToHalProgramInfo(modified.get(i)); + } + + ProgramIdentifier[] halRemoved = + new android.hardware.broadcastradio.ProgramIdentifier[removed.size()]; + for (int i = 0; i < removed.size(); i++) { + halRemoved[i] = ConversionUtils.identifierToHalProgramIdentifier(removed.get(i)); + } + return makeHalChunk(purge, /* complete= */ true, halModified, halRemoved); + } + + static ProgramListChunk makeHalChunk(boolean purge, boolean complete, ProgramInfo[] modified, ProgramIdentifier[] removed) { ProgramListChunk halChunk = new ProgramListChunk(); halChunk.purge = purge; @@ -118,6 +147,22 @@ final class AidlTestUtils { return halChunk; } + static ProgramList.Chunk makeChunk(boolean purge, + List modified, + List removed) throws RemoteException { + ArraySet modifiedSet = new ArraySet<>(); + if (modified != null) { + modifiedSet.addAll(modified); + } + ArraySet removedSet = new ArraySet<>(); + if (removed != null) { + removedSet.addAll(removed); + } + ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ purge, /* complete= */true, + modifiedSet, removedSet); + return chunk; + } + static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) { VendorKeyValue vendorKeyValue = new VendorKeyValue(); vendorKeyValue.key = vendorKey; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 710c150c006cf..5d0e07613a98f 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -328,7 +328,7 @@ public final class ConversionUtilsTest { TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY); RadioManager.ProgramInfo dabInfo = ConversionUtils.programInfoFromHalProgramInfo(halDabInfo); - ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete, + ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(purge, complete, new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_VENDOR_ID, TEST_HAL_FM_FREQUENCY_ID}); @@ -353,7 +353,7 @@ public final class ConversionUtilsTest { TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID}); ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector, TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY); - ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete, + ProgramListChunk halChunk = AidlTestUtils.makeHalChunk(purge, complete, new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_FM_FREQUENCY_ID}); ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index d7723acf6f05f..62846b1c4cc0a 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -37,7 +37,9 @@ import android.graphics.Bitmap; import android.hardware.broadcastradio.IBroadcastRadio; import android.hardware.broadcastradio.ITunerCallback; import android.hardware.broadcastradio.IdentifierType; +import android.hardware.broadcastradio.ProgramFilter; import android.hardware.broadcastradio.ProgramInfo; +import android.hardware.broadcastradio.ProgramListChunk; import android.hardware.broadcastradio.Result; import android.hardware.broadcastradio.VendorKeyValue; import android.hardware.radio.ProgramList; @@ -61,8 +63,10 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.verification.VerificationWithTimeout; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * Tests for AIDL HAL TunerSession. @@ -72,7 +76,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 200); - private static final int SIGNAL_QUALITY = 1; + private static final int SIGNAL_QUALITY = 90; private static final long AM_FM_FREQUENCY_SPACING = 500; private static final long[] AM_FM_FREQUENCY_LIST = {97_500, 98_100, 99_100}; private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR = @@ -84,6 +88,27 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR); private static final int UNSUPPORTED_CONFIG_FLAG = 0; + private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, + /* value= */ 88_500); + private static final ProgramSelector.Identifier TEST_RDS_PI_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, + /* value= */ 15_019); + + private static final RadioManager.ProgramInfo TEST_FM_INFO = AidlTestUtils.makeProgramInfo( + AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, + TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID, TEST_FM_FREQUENCY_ID, + SIGNAL_QUALITY); + private static final RadioManager.ProgramInfo TEST_FM_INFO_MODIFIED = + AidlTestUtils.makeProgramInfo(AidlTestUtils.makeProgramSelector( + ProgramSelector.PROGRAM_TYPE_FM, TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID, + TEST_FM_FREQUENCY_ID, /* signalQuality= */ 100); + private static final RadioManager.ProgramInfo TEST_RDS_INFO = AidlTestUtils.makeProgramInfo( + AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_PI_ID), + TEST_RDS_PI_ID, new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 89_500), + SIGNAL_QUALITY); + // Mocks @Mock private IBroadcastRadio mBroadcastRadioMock; private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks; @@ -393,7 +418,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - public void tune_withHalHasUnknownError_fails() throws Exception { + public void tune_withUnknownErrorFromHal_fails() throws Exception { openAidlClients(/* numClients= */ 1); ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR)) @@ -403,7 +428,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].tune(sel); }); - assertWithMessage("Exception for tuning when HAL has unknown error") + assertWithMessage("Unknown error HAL exception when tuning") .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR"); } @@ -536,7 +561,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - public void seek_withHalHasInternalError_fails() throws Exception { + public void seek_withInternalErrorFromHal_fails() throws Exception { openAidlClients(/* numClients= */ 1); doThrow(new ServiceSpecificException(Result.INTERNAL_ERROR)) .when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean()); @@ -545,7 +570,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false); }); - assertWithMessage("Exception for seeking when HAL has internal error") + assertWithMessage("Internal error HAL exception when seeking") .that(thrown).hasMessageThat().contains("INTERNAL_ERROR"); } @@ -643,12 +668,276 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onBackgroundScanComplete(); } + @Test + public void startProgramListUpdates_withEmptyFilter() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(filter); + List modified = List.of(TEST_FM_INFO, TEST_RDS_INFO); + List removed = new ArrayList<>(); + ProgramListChunk halProgramList = + AidlTestUtils.makeHalChunk(/* purge= */ true, modified, removed); + ProgramList.Chunk expectedProgramList = + AidlTestUtils.makeChunk(/* purge= */ true, modified, removed); + + mTunerSessions[0].startProgramListUpdates(filter); + mHalTunerCallback.onProgramListUpdated(halProgramList); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onProgramListUpdated(expectedProgramList); + } + + @Test + public void startProgramListUpdates_withCallbackCalledForMultipleTimes() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + mTunerSessions[0].startProgramListUpdates(filter); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), + new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), + List.of(TEST_RDS_PI_ID))); + } + + @Test + public void startProgramListUpdates_withTheSameFilterForMultipleTimes() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + mTunerSessions[0].startProgramListUpdates(filter); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), + new ArrayList<>())); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), + List.of(TEST_RDS_PI_ID))); + + mTunerSessions[0].startProgramListUpdates(filter); + + verify(mBroadcastRadioMock).startProgramListUpdates(any()); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO_MODIFIED), + new ArrayList<>())); + } + + @Test + public void startProgramListUpdates_withNullFilter() throws Exception { + openAidlClients(/* numClients= */ 1); + + mTunerSessions[0].startProgramListUpdates(/* filter= */ null); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(any()); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), + new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), + List.of(TEST_RDS_PI_ID))); + } + + @Test + public void startProgramListUpdates_withIdFilter() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter idFilter = new ProgramList.Filter(new ArraySet<>(), + Set.of(TEST_RDS_PI_ID), /* includeCategories= */ true, + /* excludeModifications= */ true); + ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(idFilter); + + mTunerSessions[0].startProgramListUpdates(idFilter); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_RDS_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_RDS_INFO), + new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any()); + } + + @Test + public void startProgramListUpdates_withFilterExcludingModifications() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filterExcludingModifications = new ProgramList.Filter( + Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ true); + ProgramFilter halFilter = + ConversionUtils.filterToHalProgramFilter(filterExcludingModifications); + + mTunerSessions[0].startProgramListUpdates(filterExcludingModifications); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO), + new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any()); + } + + @Test + public void startProgramListUpdates_withFilterIncludingModifications() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filterIncludingModifications = new ProgramList.Filter( + Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + ProgramFilter halFilter = + ConversionUtils.filterToHalProgramFilter(filterIncludingModifications); + + mTunerSessions[0].startProgramListUpdates(filterIncludingModifications); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO), + new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), + new ArrayList<>())); + } + + @Test + public void onProgramListUpdated_afterSessionClosed_doesNotUpdates() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + mTunerSessions[0].startProgramListUpdates(filter); + + mTunerSessions[0].close(); + + verify(mBroadcastRadioMock).stopProgramListUpdates(); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onProgramListUpdated(any()); + } + + @Test + public void startProgramListUpdates_forMultipleSessions() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + ProgramList.Filter fmIdFilter = new ProgramList.Filter(new ArraySet<>(), + Set.of(TEST_FM_FREQUENCY_ID), /* includeCategories= */ false, + /* excludeModifications= */ true); + ProgramList.Filter filterExcludingCategories = new ProgramList.Filter(new ArraySet<>(), + new ArraySet<>(), /* includeCategories= */ true, + /* excludeModifications= */ true); + ProgramList.Filter rdsTypeFilter = new ProgramList.Filter( + Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + + mTunerSessions[0].startProgramListUpdates(fmIdFilter); + + ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(fmIdFilter); + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + + mTunerSessions[1].startProgramListUpdates(filterExcludingCategories); + + halFilter.identifiers = new android.hardware.broadcastradio.ProgramIdentifier[]{}; + halFilter.includeCategories = true; + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + + mTunerSessions[2].startProgramListUpdates(rdsTypeFilter); + + halFilter.excludeModifications = false; + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + } + + @Test + public void onProgramListUpdated_forMultipleSessions() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + List filters = List.of(new ProgramList.Filter( + Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false), + new ProgramList.Filter(new ArraySet<>(), Set.of(TEST_FM_FREQUENCY_ID), + /* includeCategories= */ false, /* excludeModifications= */ true), + new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ true)); + + for (int index = 0; index < numSessions; index++) { + mTunerSessions[index].startProgramListUpdates(filters.get(index)); + } + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, + List.of(TEST_RDS_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[1], CALLBACK_TIMEOUT) + .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, + List.of(TEST_FM_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[2], CALLBACK_TIMEOUT) + .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, + List.of(TEST_RDS_INFO, TEST_FM_INFO), new ArrayList<>())); + } + + @Test + public void startProgramListUpdates_forNonCurrentUser_doesNotStartUpdates() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); + + mTunerSessions[0].startProgramListUpdates(filter); + + verify(mBroadcastRadioMock, never()).startProgramListUpdates(any()); + } + + @Test + public void startProgramListUpdates_withUnknownErrorFromHal_fails() throws Exception { + openAidlClients(/* numClients= */ 1); + doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR)) + .when(mBroadcastRadioMock).startProgramListUpdates(any()); + + ParcelableException thrown = assertThrows(ParcelableException.class, () -> { + mTunerSessions[0].startProgramListUpdates(/* filter= */ null); + }); + + assertWithMessage("Unknown error HAL exception when updating program list") + .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR"); + } + @Test public void stopProgramListUpdates() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), /* includeCategories= */ true, /* excludeModifications= */ false); - mTunerSessions[0].startProgramListUpdates(aidlFilter); + mTunerSessions[0].startProgramListUpdates(filter); mTunerSessions[0].stopProgramListUpdates(); @@ -658,9 +947,9 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), /* includeCategories= */ true, /* excludeModifications= */ false); - mTunerSessions[0].startProgramListUpdates(aidlFilter); + mTunerSessions[0].startProgramListUpdates(filter); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].stopProgramListUpdates(); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index ea9a8461ad928..3815008bd4fb3 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -373,7 +373,7 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { } @Test - public void tune_withHalHasUnknownError_fails() throws Exception { + public void tune_withUnknownErrorFromHal_fails() throws Exception { openAidlClients(/* numClients= */ 1); ProgramSelector sel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); doAnswer(invocation -> Result.UNKNOWN_ERROR).when(mHalTunerSessionMock).tune(any()); @@ -382,7 +382,7 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].tune(sel); }); - assertWithMessage("Exception for tuning when HAL has unknown error") + assertWithMessage("Unknown error HAL exception when tuning") .that(thrown).hasMessageThat().contains(Result.toString(Result.UNKNOWN_ERROR)); } @@ -513,7 +513,7 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { } @Test - public void seek_withHalHasInternalError_fails() throws Exception { + public void seek_withInternalErrorFromHal_fails() throws Exception { openAidlClients(/* numClients= */ 1); doAnswer(invocation -> Result.INTERNAL_ERROR).when(mHalTunerSessionMock) .scan(anyBoolean(), anyBoolean()); @@ -522,7 +522,7 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false); }); - assertWithMessage("Exception for seeking when HAL has internal error") + assertWithMessage("Internal error HAL exception when seeking") .that(thrown).hasMessageThat().contains(Result.toString(Result.INTERNAL_ERROR)); } @@ -632,6 +632,32 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onBackgroundScanComplete(); } + @Test + public void startProgramListUpdates_forNonCurrentUser_doesNotStartUpdates() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); + + mTunerSessions[0].startProgramListUpdates(filter); + + verify(mHalTunerSessionMock, never()).startProgramListUpdates(any()); + } + + @Test + public void startProgramListUpdates_withUnknownErrorFromHal_fails() throws Exception { + openAidlClients(/* numClients= */ 1); + doAnswer(invocation -> Result.UNKNOWN_ERROR).when(mHalTunerSessionMock) + .startProgramListUpdates(any()); + + ParcelableException thrown = assertThrows(ParcelableException.class, () -> { + mTunerSessions[0].startProgramListUpdates(/* filter= */ null); + }); + + assertWithMessage("Unknown error HAL exception when updating program list") + .that(thrown).hasMessageThat().contains(Result.toString(Result.UNKNOWN_ERROR)); + } + @Test public void stopProgramListUpdates() throws Exception { openAidlClients(/* numClients= */ 1); From 52bbbd47dfed7e2b51613af02d189c2e68ef800d Mon Sep 17 00:00:00 2001 From: Weilin Xu Date: Tue, 13 Sep 2022 18:21:19 +0000 Subject: [PATCH 2/2] Add test for AIDL radio service program info cache Added unit tests for program info cache of AIDL broadcast radio HAL client. Bug: 241118988 Test: com.android.server.broadcastradio.aidl Change-Id: I6796917f95644e4e606ef8bfeefdb9d6e7c05437 --- .../broadcastradio/aidl/AidlTestUtils.java | 11 +- .../aidl/ProgramInfoCacheTest.java | 409 ++++++++++++++++++ .../broadcastradio/aidl/TunerSessionTest.java | 85 ++-- .../broadcastradio/aidl/ProgramInfoCache.java | 8 +- 4 files changed, 459 insertions(+), 54 deletions(-) create mode 100644 core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java index e2e9bd9695553..cce1b2bc3ece9 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java @@ -121,8 +121,8 @@ final class AidlTestUtils { return hwInfo; } - static ProgramListChunk makeHalChunk(boolean purge, List modified, - List removed) { + static ProgramListChunk makeHalChunk(boolean purge, boolean complete, + List modified, List removed) { ProgramInfo[] halModified = new android.hardware.broadcastradio.ProgramInfo[modified.size()]; for (int i = 0; i < modified.size(); i++) { @@ -134,7 +134,7 @@ final class AidlTestUtils { for (int i = 0; i < removed.size(); i++) { halRemoved[i] = ConversionUtils.identifierToHalProgramIdentifier(removed.get(i)); } - return makeHalChunk(purge, /* complete= */ true, halModified, halRemoved); + return makeHalChunk(purge, complete, halModified, halRemoved); } static ProgramListChunk makeHalChunk(boolean purge, boolean complete, @@ -147,7 +147,7 @@ final class AidlTestUtils { return halChunk; } - static ProgramList.Chunk makeChunk(boolean purge, + static ProgramList.Chunk makeChunk(boolean purge, boolean complete, List modified, List removed) throws RemoteException { ArraySet modifiedSet = new ArraySet<>(); @@ -158,8 +158,7 @@ final class AidlTestUtils { if (removed != null) { removedSet.addAll(removed); } - ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ purge, /* complete= */true, - modifiedSet, removedSet); + ProgramList.Chunk chunk = new ProgramList.Chunk(purge, complete, modifiedSet, removedSet); return chunk; } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java new file mode 100644 index 0000000000000..d54397e07a636 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ProgramInfoCacheTest.java @@ -0,0 +1,409 @@ +/* + * 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 android.hardware.broadcastradio.ProgramIdentifier; +import android.hardware.broadcastradio.ProgramInfo; +import android.hardware.broadcastradio.ProgramListChunk; +import android.hardware.radio.ProgramList; +import android.hardware.radio.ProgramSelector; +import android.hardware.radio.RadioManager; +import android.os.RemoteException; +import android.util.ArraySet; + +import com.google.common.truth.Expect; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; +import java.util.Set; + +/** + * Unit tests for AIDL ProgramInfoCache + */ +@RunWith(MockitoJUnitRunner.class) +public class ProgramInfoCacheTest { + + private static final int TEST_SIGNAL_QUALITY = 90; + + private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, + /* value= */ 88_500); + private static final RadioManager.ProgramInfo TEST_FM_INFO = AidlTestUtils.makeProgramInfo( + AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, + TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID, TEST_FM_FREQUENCY_ID, + TEST_SIGNAL_QUALITY); + private static final RadioManager.ProgramInfo TEST_FM_INFO_MODIFIED = + AidlTestUtils.makeProgramInfo(AidlTestUtils.makeProgramSelector( + ProgramSelector.PROGRAM_TYPE_FM, TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID, + TEST_FM_FREQUENCY_ID, /* signalQuality= */ 99); + + private static final ProgramSelector.Identifier TEST_AM_FREQUENCY_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, + /* value= */ 1_700); + private static final RadioManager.ProgramInfo TEST_AM_INFO = AidlTestUtils.makeProgramInfo( + AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, + TEST_AM_FREQUENCY_ID), TEST_AM_FREQUENCY_ID, TEST_AM_FREQUENCY_ID, + TEST_SIGNAL_QUALITY); + + private static final ProgramSelector.Identifier TEST_RDS_PI_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, + /* value= */ 15_019); + private static final RadioManager.ProgramInfo TEST_RDS_INFO = AidlTestUtils.makeProgramInfo( + AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_PI_ID), + TEST_RDS_PI_ID, new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 89_500), + TEST_SIGNAL_QUALITY); + + private static final ProgramSelector.Identifier TEST_DAB_DMB_SID_EXT_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, + /* value= */ 0xA000000111L); + private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, + /* value= */ 0x1001); + private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, + /* value= */ 220_352); + private static final RadioManager.ProgramInfo TEST_DAB_INFO = AidlTestUtils.makeProgramInfo( + new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_DMB_SID_EXT_ID, + new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID}, + /* vendorIds= */ null), TEST_DAB_DMB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, + TEST_SIGNAL_QUALITY); + + private static final ProgramSelector.Identifier TEST_VENDOR_ID = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, + /* value= */ 9_001); + private static final RadioManager.ProgramInfo TEST_VENDOR_INFO = AidlTestUtils.makeProgramInfo( + AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_VENDOR_START, + TEST_VENDOR_ID), TEST_VENDOR_ID, TEST_VENDOR_ID, TEST_SIGNAL_QUALITY); + + private static final ProgramInfoCache FULL_PROGRAM_INFO_CACHE = new ProgramInfoCache( + /* filter= */ null, /* complete= */ true, + TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, TEST_VENDOR_INFO); + + @Rule + public final Expect expect = Expect.create(); + + @Test + public void isComplete_forCompleteProgramInfoCache_returnsTrue() { + expect.withMessage("Complete program info cache") + .that(FULL_PROGRAM_INFO_CACHE.isComplete()).isTrue(); + } + + @Test + public void isComplete_forIncompleteProgramInfoCache_returnsFalse() { + ProgramInfoCache programInfoCache = new ProgramInfoCache(/* filter= */ null, + /* complete= */ false); + expect.withMessage("Incomplete program info cache") + .that(programInfoCache.isComplete()).isFalse(); + } + + @Test + public void getFilter_forProgramInfoCache() { + ProgramList.Filter fmFilter = new ProgramList.Filter( + Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + ProgramInfoCache fmProgramInfoCache = new ProgramInfoCache(fmFilter); + + expect.withMessage("Program info cache filter") + .that(fmProgramInfoCache.getFilter()).isEqualTo(fmFilter); + } + + @Test + public void updateFromHalProgramListChunk_withPurgingCompleteChunk() { + ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, + /* complete= */ false, TEST_FM_INFO); + ProgramListChunk chunk = AidlTestUtils.makeHalChunk(/* purge= */ true, /* complete= */ true, + new ProgramInfo[]{AidlTestUtils.programInfoToHalProgramInfo(TEST_RDS_INFO), + AidlTestUtils.programInfoToHalProgramInfo(TEST_VENDOR_INFO)}, + new ProgramIdentifier[]{}); + + cache.updateFromHalProgramListChunk(chunk); + + expect.withMessage("Program cache updated with purge-enabled and complete chunk") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_RDS_INFO, TEST_VENDOR_INFO); + expect.withMessage("Complete program cache").that(cache.isComplete()).isTrue(); + } + + @Test + public void updateFromHalProgramListChunk_withNonPurgingIncompleteChunk() { + ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, + /* complete= */ false, TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO); + ProgramListChunk chunk = AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ false, + new ProgramInfo[]{AidlTestUtils.programInfoToHalProgramInfo(TEST_FM_INFO_MODIFIED), + AidlTestUtils.programInfoToHalProgramInfo(TEST_VENDOR_INFO)}, + new ProgramIdentifier[]{ConversionUtils.identifierToHalProgramIdentifier( + TEST_RDS_PI_ID)}); + + cache.updateFromHalProgramListChunk(chunk); + + expect.withMessage("Program cache updated with non-purging and incomplete chunk") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO, TEST_AM_INFO); + expect.withMessage("Incomplete program cache").that(cache.isComplete()).isFalse(); + } + + @Test + public void filterAndUpdateFromInternal_withNullFilter() { + ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, + /* complete= */ true); + + cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, /* purge= */ false); + + expect.withMessage("Program cache filtered by null filter") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, + TEST_VENDOR_INFO); + } + + @Test + public void filterAndUpdateFromInternal_withEmptyFilter() { + ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(), + new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false)); + + cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, /* purge= */ false); + + expect.withMessage("Program cache filtered by empty filter") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO, + TEST_VENDOR_INFO); + } + + @Test + public void filterAndUpdateFromInternal_withFilterByIdentifierType() { + ProgramInfoCache cache = new ProgramInfoCache( + new ProgramList.Filter(Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, + ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false)); + + cache.filterAndUpdateFromInternal(FULL_PROGRAM_INFO_CACHE, /* purge= */ false); + + expect.withMessage("Program cache filtered by identifier type") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO); + } + + @Test + public void filterAndUpdateFromInternal_withFilterByIdentifier() { + ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter( + new ArraySet<>(), Set.of(TEST_FM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID), + /* includeCategories= */ true, /* excludeModifications= */ false)); + int maxNumModifiedPerChunk = 2; + int maxNumRemovedPerChunk = 2; + + List programListChunks = cache.filterAndUpdateFromInternal( + FULL_PROGRAM_INFO_CACHE, /* purge= */ true, maxNumModifiedPerChunk, + maxNumRemovedPerChunk); + + expect.withMessage("Program cache filtered by identifier") + .that(cache.toProgramInfoList()).containsExactly(TEST_FM_INFO, TEST_DAB_INFO); + verifyChunkListPurge(programListChunks, /* purge= */ true); + verifyChunkListComplete(programListChunks, FULL_PROGRAM_INFO_CACHE.isComplete()); + verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO, + TEST_DAB_INFO); + verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk); + } + + @Test + public void filterAndUpdateFromInternal_withFilterExcludingCategories() { + ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(), + new ArraySet<>(), /* includeCategories= */ false, + /* excludeModifications= */ false)); + int maxNumModifiedPerChunk = 3; + int maxNumRemovedPerChunk = 2; + + List programListChunks = cache.filterAndUpdateFromInternal( + FULL_PROGRAM_INFO_CACHE, /* purge= */ false, maxNumModifiedPerChunk, + maxNumRemovedPerChunk); + + expect.withMessage("Program cache filtered by excluding categories") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO, TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO); + verifyChunkListPurge(programListChunks, /* purge= */ true); + verifyChunkListComplete(programListChunks, FULL_PROGRAM_INFO_CACHE.isComplete()); + verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO, + TEST_AM_INFO, TEST_RDS_INFO, TEST_DAB_INFO); + verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk); + } + + @Test + public void filterAndUpdateFromInternal_withFilterExcludingModifications() { + ProgramList.Filter filterExcludingModifications = new ProgramList.Filter(new ArraySet<>(), + new ArraySet<>(), /* includeCategories= */ true, + /* excludeModifications= */ true); + ProgramInfoCache cache = new ProgramInfoCache(filterExcludingModifications, + /* complete= */ true, TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO, TEST_DAB_INFO); + ProgramInfoCache halCache = new ProgramInfoCache(/* filter= */ null, /* complete= */ false, + TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO); + int maxNumModifiedPerChunk = 2; + int maxNumRemovedPerChunk = 2; + + List programListChunks = cache.filterAndUpdateFromInternal(halCache, + /* purge= */ false, maxNumModifiedPerChunk, maxNumRemovedPerChunk); + + expect.withMessage("Program cache filtered by excluding modifications") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO, TEST_VENDOR_INFO); + verifyChunkListPurge(programListChunks, /* purge= */ false); + verifyChunkListComplete(programListChunks, halCache.isComplete()); + verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_VENDOR_INFO); + verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk, TEST_RDS_PI_ID, + TEST_AM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID); + } + + @Test + public void filterAndUpdateFromInternal_withPurge() { + ProgramInfoCache cache = new ProgramInfoCache(new ProgramList.Filter(new ArraySet<>(), + new ArraySet<>(), /* includeCategories= */ true, + /* excludeModifications= */ false), + /* complete= */ true, TEST_FM_INFO, TEST_RDS_INFO); + ProgramInfoCache halCache = new ProgramInfoCache(/* filter= */ null, /* complete= */ false, + TEST_FM_INFO_MODIFIED, TEST_DAB_INFO, TEST_VENDOR_INFO); + int maxNumModifiedPerChunk = 2; + int maxNumRemovedPerChunk = 2; + + List programListChunks = cache.filterAndUpdateFromInternal(halCache, + /* purge= */ true, maxNumModifiedPerChunk, maxNumRemovedPerChunk); + + expect.withMessage("Purged program cache").that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO_MODIFIED, TEST_DAB_INFO, TEST_VENDOR_INFO); + verifyChunkListPurge(programListChunks, /* purge= */ true); + verifyChunkListComplete(programListChunks, halCache.isComplete()); + verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED, + TEST_DAB_INFO, TEST_VENDOR_INFO); + verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk); + } + + @Test + public void filterAndApplyChunkInternal_withPurgingIncompleteChunk() throws RemoteException { + ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, + /* complete= */ false, TEST_FM_INFO, TEST_DAB_INFO); + ProgramList.Chunk chunk = AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ false, + List.of(TEST_FM_INFO_MODIFIED, TEST_RDS_INFO, TEST_VENDOR_INFO), + List.of(TEST_DAB_DMB_SID_EXT_ID)); + int maxNumModifiedPerChunk = 2; + int maxNumRemovedPerChunk = 2; + + List programListChunks = cache.filterAndApplyChunkInternal(chunk, + maxNumModifiedPerChunk, maxNumRemovedPerChunk); + + expect.withMessage("Program cache applied with non-purging and complete chunk") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO_MODIFIED, TEST_RDS_INFO, TEST_VENDOR_INFO); + verifyChunkListPurge(programListChunks, /* purge= */ true); + verifyChunkListComplete(programListChunks, /* complete= */ false); + verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED, + TEST_RDS_INFO, TEST_VENDOR_INFO); + verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk); + } + + @Test + public void filterAndApplyChunk_withNonPurgingCompleteChunk() throws RemoteException { + ProgramInfoCache cache = new ProgramInfoCache(/* filter= */ null, + /* complete= */ false, TEST_FM_INFO, TEST_RDS_INFO, TEST_AM_INFO, TEST_DAB_INFO); + ProgramList.Chunk chunk = AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO), + List.of(TEST_RDS_PI_ID, TEST_AM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID)); + int maxNumModifiedPerChunk = 2; + int maxNumRemovedPerChunk = 2; + + List programListChunks = cache.filterAndApplyChunkInternal(chunk, + maxNumModifiedPerChunk, maxNumRemovedPerChunk); + + expect.withMessage("Program cache applied with purge-enabled complete chunk") + .that(cache.toProgramInfoList()) + .containsExactly(TEST_FM_INFO_MODIFIED, TEST_VENDOR_INFO); + verifyChunkListPurge(programListChunks, /* purge= */ false); + verifyChunkListComplete(programListChunks, /* complete= */ true); + verifyChunkListModified(programListChunks, maxNumModifiedPerChunk, TEST_FM_INFO_MODIFIED, + TEST_VENDOR_INFO); + verifyChunkListRemoved(programListChunks, maxNumRemovedPerChunk, TEST_RDS_PI_ID, + TEST_AM_FREQUENCY_ID, TEST_DAB_DMB_SID_EXT_ID); + } + + private void verifyChunkListPurge(List chunks, boolean purge) { + if (chunks.isEmpty()) { + return; + } + for (int i = 0; i < chunks.size(); i++) { + ProgramList.Chunk chunk = chunks.get(i); + boolean expectedPurge = (i == 0 && purge); + + expect.withMessage("Purge for chunk %s", i) + .that(chunk.isPurge()).isEqualTo(expectedPurge); + } + } + + private void verifyChunkListComplete(List chunks, boolean complete) { + if (chunks.isEmpty()) { + return; + } + for (int i = 0; i < chunks.size(); i++) { + ProgramList.Chunk chunk = chunks.get(i); + boolean expectedComplete = (i == chunks.size() - 1 && complete); + + expect.withMessage("Purge for chunk %s", i) + .that(chunk.isComplete()).isEqualTo(expectedComplete); + } + } + + private void verifyChunkListModified(List chunks, + int maxModifiedPerChunk, RadioManager.ProgramInfo... expectedProgramInfos) { + if (chunks.isEmpty()) { + expect.withMessage("Empty program info list") + .that(expectedProgramInfos.length).isEqualTo(0); + return; + } + + ArraySet actualSet = new ArraySet<>(); + for (int i = 0; i < chunks.size(); i++) { + Set chunkModified = chunks.get(i).getModified(); + actualSet.addAll(chunkModified); + + expect.withMessage("Chunk %s modified program info array size", i) + .that(chunkModified.size()).isAtMost(maxModifiedPerChunk); + } + expect.withMessage("Program info items") + .that(actualSet).containsExactlyElementsIn(expectedProgramInfos); + } + + private void verifyChunkListRemoved(List chunks, + int maxRemovedPerChunk, ProgramSelector.Identifier... expectedIdentifiers) { + if (chunks.isEmpty()) { + expect.withMessage("Empty program info list") + .that(expectedIdentifiers.length).isEqualTo(0); + return; + } + + ArraySet actualSet = new ArraySet<>(); + for (int i = 0; i < chunks.size(); i++) { + Set chunkRemoved = chunks.get(i).getRemoved(); + actualSet.addAll(chunkRemoved); + + expect.withMessage("Chunk %s removed identifier array size ", i) + .that(chunkRemoved.size()).isAtMost(maxRemovedPerChunk); + } + expect.withMessage("Removed identifier items") + .that(actualSet).containsExactlyElementsIn(expectedIdentifiers); + } +} diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 62846b1c4cc0a..464ecb2b50a11 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -676,10 +676,10 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(filter); List modified = List.of(TEST_FM_INFO, TEST_RDS_INFO); List removed = new ArrayList<>(); - ProgramListChunk halProgramList = - AidlTestUtils.makeHalChunk(/* purge= */ true, modified, removed); + ProgramListChunk halProgramList = AidlTestUtils.makeHalChunk(/* purge= */ true, + /* complete= */ true, modified, removed); ProgramList.Chunk expectedProgramList = - AidlTestUtils.makeChunk(/* purge= */ true, modified, removed); + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modified, removed); mTunerSessions[0].startProgramListUpdates(filter); mHalTunerCallback.onProgramListUpdated(halProgramList); @@ -696,17 +696,17 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { /* includeCategories= */ true, /* excludeModifications= */ false); mTunerSessions[0].startProgramListUpdates(filter); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true, - List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), - List.of(TEST_RDS_PI_ID))); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); } @Test @@ -716,22 +716,22 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { /* includeCategories= */ true, /* excludeModifications= */ false); mTunerSessions[0].startProgramListUpdates(filter); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true, - List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), - List.of(TEST_RDS_PI_ID))); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); mTunerSessions[0].startProgramListUpdates(filter); verify(mBroadcastRadioMock).startProgramListUpdates(any()); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO_MODIFIED), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, + List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); } @Test @@ -740,19 +740,19 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].startProgramListUpdates(/* filter= */ null); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true, - List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); verify(mBroadcastRadioMock).startProgramListUpdates(any()); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), - List.of(TEST_RDS_PI_ID))); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); } @Test @@ -765,15 +765,15 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].startProgramListUpdates(idFilter); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_RDS_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>())); verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_RDS_INFO), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_RDS_INFO), new ArrayList<>())); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any()); } @@ -789,15 +789,15 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].startProgramListUpdates(filterExcludingModifications); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO), new ArrayList<>())); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any()); } @@ -813,19 +813,19 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { mTunerSessions[0].startProgramListUpdates(filterIncludingModifications); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO), new ArrayList<>())); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( - AidlTestUtils.makeChunk(/* purge= */ false, List.of(TEST_FM_INFO_MODIFIED), - new ArrayList<>())); + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); } @Test @@ -840,7 +840,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { verify(mBroadcastRadioMock).stopProgramListUpdates(); mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onProgramListUpdated(any()); } @@ -893,17 +893,18 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, - List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, - List.of(TEST_RDS_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[1], CALLBACK_TIMEOUT) .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, - List.of(TEST_FM_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); verify(mAidlTunerCallbackMocks[2], CALLBACK_TIMEOUT) .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, - List.of(TEST_RDS_INFO, TEST_FM_INFO), new ArrayList<>())); + /* complete= */ true, List.of(TEST_RDS_INFO, TEST_FM_INFO), + new ArrayList<>())); } @Test diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java index 39b13547cf930..c9ae735d63e20 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java @@ -86,12 +86,8 @@ final class ProgramInfoCache { } @VisibleForTesting - boolean programInfosAreExactly(RadioManager.ProgramInfo... programInfos) { - Map expectedMap = new ArrayMap<>(); - for (int i = 0; i < programInfos.length; i++) { - expectedMap.put(programInfos[i].getSelector().getPrimaryId(), programInfos[i]); - } - return expectedMap.equals(mProgramInfoMap); + List toProgramInfoList() { + return new ArrayList<>(mProgramInfoMap.values()); } @Override