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..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 @@ -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, boolean complete, + 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, halModified, halRemoved); + } + + static ProgramListChunk makeHalChunk(boolean purge, boolean complete, ProgramInfo[] modified, ProgramIdentifier[] removed) { ProgramListChunk halChunk = new ProgramListChunk(); halChunk.purge = purge; @@ -118,6 +147,21 @@ final class AidlTestUtils { return halChunk; } + static ProgramList.Chunk makeChunk(boolean purge, boolean complete, + 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, complete, 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/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 d7723acf6f05f..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 @@ -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,277 @@ 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, + /* complete= */ true, modified, removed); + ProgramList.Chunk expectedProgramList = + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ 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, + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + 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, + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + 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, /* complete= */ 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, + /* 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, /* complete= */ true, + List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID))); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + 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, + /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_RDS_INFO), new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ true, 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, + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO), new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ true, 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, + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); + + verify(mBroadcastRadioMock).startProgramListUpdates(halFilter); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + List.of(TEST_FM_INFO), new ArrayList<>())); + + mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false, + /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated( + AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true, + 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, + /* complete= */ true, 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, + /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>())); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, + /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[1], CALLBACK_TIMEOUT) + .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, + /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>())); + verify(mAidlTunerCallbackMocks[2], CALLBACK_TIMEOUT) + .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false, + /* complete= */ true, 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 +948,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); 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