1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.broadcastradio.hal2; 17 18 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 19 20 import static org.junit.Assert.*; 21 import static org.mockito.Matchers.any; 22 import static org.mockito.Mockito.doAnswer; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.timeout; 25 import static org.mockito.Mockito.times; 26 import static org.mockito.Mockito.verify; 27 import static org.mockito.Mockito.when; 28 29 import android.hardware.broadcastradio.V2_0.IBroadcastRadio; 30 import android.hardware.broadcastradio.V2_0.ITunerCallback; 31 import android.hardware.broadcastradio.V2_0.ITunerSession; 32 import android.hardware.broadcastradio.V2_0.ProgramFilter; 33 import android.hardware.broadcastradio.V2_0.ProgramListChunk; 34 import android.hardware.broadcastradio.V2_0.Result; 35 import android.hardware.radio.ProgramList; 36 import android.hardware.radio.ProgramSelector; 37 import android.hardware.radio.RadioManager; 38 import android.hardware.radio.UniqueProgramIdentifier; 39 import android.os.RemoteException; 40 41 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; 42 import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; 43 import com.android.server.broadcastradio.RadioServiceUserController; 44 45 import org.junit.Before; 46 import org.junit.Test; 47 import org.mockito.Mock; 48 import org.mockito.stubbing.Answer; 49 import org.mockito.verification.VerificationWithTimeout; 50 51 import java.util.Arrays; 52 import java.util.HashSet; 53 import java.util.List; 54 55 /** 56 * Tests for v2 HAL RadioModule. 57 */ 58 public class StartProgramListUpdatesFanoutTest extends ExtendedRadioMockitoTestCase { 59 private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout"; 60 61 private static final VerificationWithTimeout CB_TIMEOUT = timeout(500); 62 63 // Mocks 64 @Mock IBroadcastRadio mBroadcastRadioMock; 65 @Mock ITunerSession mHalTunerSessionMock; 66 private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks; 67 68 // RadioModule under test 69 private RadioModule mRadioModule; 70 71 // Objects created by mRadioModule 72 private ITunerCallback mHalTunerCallback; 73 private TunerSession[] mTunerSessions; 74 75 // Data objects used during tests 76 private static final int TEST_QUALITY = 0; 77 private static final ProgramSelector.Identifier TEST_AM_FM_ID = 78 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, 79 /* value= */ 88_500); 80 private static final ProgramSelector TEST_AM_FM_SELECTOR = TestUtils.makeProgramSelector( 81 ProgramSelector.PROGRAM_TYPE_FM, TEST_AM_FM_ID); 82 private static final RadioManager.ProgramInfo TEST_AM_FM_INFO = TestUtils.makeProgramInfo( 83 TEST_AM_FM_SELECTOR, TEST_QUALITY); 84 private static final RadioManager.ProgramInfo TEST_AM_FM_MODIFIED_INFO = 85 TestUtils.makeProgramInfo(TEST_AM_FM_SELECTOR, TEST_QUALITY + 1); 86 87 private static final ProgramSelector.Identifier TEST_RDS_ID = 88 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 89 /* value= */ 15_019); 90 private static final ProgramSelector TEST_RDS_SELECTOR = TestUtils.makeProgramSelector( 91 ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_ID); 92 93 private static final UniqueProgramIdentifier TEST_RDS_UNIQUE_ID = new UniqueProgramIdentifier( 94 TEST_RDS_ID); 95 private static final RadioManager.ProgramInfo TEST_RDS_INFO = TestUtils.makeProgramInfo( 96 TEST_RDS_SELECTOR, TEST_QUALITY); 97 98 private static final ProgramSelector.Identifier TEST_DAB_SID_ID = 99 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, 100 /* value= */ 0xA000000111L); 101 private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID = 102 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, 103 /* value= */ 0x1001); 104 private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID = 105 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, 106 /* value= */ 220_352); 107 private static final ProgramSelector TEST_DAB_SELECTOR = TestUtils.makeProgramSelector( 108 ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_ID, 109 new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID}); 110 private static final RadioManager.ProgramInfo TEST_DAB_INFO = TestUtils.makeProgramInfo( 111 TEST_DAB_SELECTOR, TEST_QUALITY); 112 113 @Override initializeSession(StaticMockitoSessionBuilder builder)114 protected void initializeSession(StaticMockitoSessionBuilder builder) { 115 builder.spyStatic(RadioServiceUserController.class); 116 } 117 118 @Before setup()119 public void setup() throws RemoteException { 120 doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); 121 122 mRadioModule = new RadioModule(mBroadcastRadioMock, 123 TestUtils.makeDefaultModuleProperties()); 124 125 doAnswer((Answer) invocation -> { 126 mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; 127 IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback) 128 invocation.getArguments()[1]; 129 cb.onValues(Result.OK, mHalTunerSessionMock); 130 return null; 131 }).when(mBroadcastRadioMock).openSession(any(), any()); 132 when(mHalTunerSessionMock.startProgramListUpdates(any())).thenReturn(Result.OK); 133 } 134 135 @Test testFanout()136 public void testFanout() throws RemoteException { 137 // Open 3 clients that will all use the same filter, and start updates on two of them for 138 // now. The HAL TunerSession should only see 1 filter update. 139 openAidlClients(3); 140 ProgramList.Filter aidlFilter = new ProgramList.Filter(new HashSet<Integer>(), 141 new HashSet<ProgramSelector.Identifier>(), true, false); 142 ProgramFilter halFilter = Convert.programFilterToHal(aidlFilter); 143 for (int i = 0; i < 2; i++) { 144 mTunerSessions[i].startProgramListUpdates(aidlFilter); 145 } 146 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 147 148 // Initiate a program list update from the HAL side and verify both connected AIDL clients 149 // receive the update. 150 updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_INFO, TEST_RDS_INFO), null); 151 for (int i = 0; i < 2; i++) { 152 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[i], true, Arrays.asList( 153 TEST_AM_FM_INFO, TEST_RDS_INFO), null); 154 } 155 156 // Repeat with a non-purging update. 157 updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), 158 Arrays.asList(TEST_RDS_ID)); 159 for (int i = 0; i < 2; i++) { 160 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[i], false, 161 Arrays.asList(TEST_AM_FM_MODIFIED_INFO), Arrays.asList(TEST_RDS_UNIQUE_ID)); 162 } 163 164 // Now start updates on the 3rd client. Verify the HAL function has not been called again 165 // and client receives the appropriate update. 166 mTunerSessions[2].startProgramListUpdates(aidlFilter); 167 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(any()); 168 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], true, 169 Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 170 } 171 172 @Test testFiltering()173 public void testFiltering() throws RemoteException { 174 // Open 4 clients that will use the following filters: 175 // [0]: ID TEST_RDS_ID, modifications excluded 176 // [1]: No categories, modifications excluded 177 // [2]: Type IDENTIFIER_TYPE_AMFM_FREQUENCY, modifications excluded 178 // [3]: Type IDENTIFIER_TYPE_AMFM_FREQUENCY, modifications included 179 openAidlClients(4); 180 ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(), 181 new HashSet<ProgramSelector.Identifier>(Arrays.asList(TEST_RDS_ID)), true, true); 182 ProgramList.Filter categoryFilter = new ProgramList.Filter(new HashSet<Integer>(), 183 new HashSet<ProgramSelector.Identifier>(), false, true); 184 ProgramList.Filter typeFilterWithoutModifications = new ProgramList.Filter( 185 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 186 new HashSet<ProgramSelector.Identifier>(), true, true); 187 ProgramList.Filter typeFilterWithModifications = new ProgramList.Filter( 188 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 189 new HashSet<ProgramSelector.Identifier>(), true, false); 190 191 // Start updates on the clients in order. The HAL filter should get updated after each 192 // client except [2]. Client [2] should update received chunk with an empty program 193 // list 194 mTunerSessions[0].startProgramListUpdates(idFilter); 195 ProgramFilter halFilter = Convert.programFilterToHal(idFilter); 196 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 197 198 mTunerSessions[1].startProgramListUpdates(categoryFilter); 199 halFilter.identifiers.clear(); 200 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 201 202 mTunerSessions[2].startProgramListUpdates(typeFilterWithoutModifications); 203 verify(mHalTunerSessionMock, times(2)).startProgramListUpdates(any()); 204 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], true, Arrays.asList(), 205 null); 206 verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 207 208 mTunerSessions[3].startProgramListUpdates(typeFilterWithModifications); 209 halFilter.excludeModifications = false; 210 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 211 212 // Adding TEST_RDS_INFO should update clients [0] and [1]. 213 updateHalProgramInfo(false, Arrays.asList(TEST_RDS_INFO), null); 214 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 215 Arrays.asList(TEST_RDS_INFO), null); 216 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, 217 Arrays.asList(TEST_RDS_INFO), null); 218 219 // Adding TEST_AM_FM_INFO should update clients [1], [2], and [3]. 220 updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_INFO), null); 221 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, 222 Arrays.asList(TEST_AM_FM_INFO), null); 223 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], false, 224 Arrays.asList(TEST_AM_FM_INFO), null); 225 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, 226 Arrays.asList(TEST_AM_FM_INFO), null); 227 228 // Modifying TEST_AM_FM_INFO to TEST_AM_FM_MODIFIED_INFO should update only [3]. 229 updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 230 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, 231 Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 232 233 updateHalProgramInfo(false, Arrays.asList(TEST_DAB_INFO), null); 234 verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 235 verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(3)).onProgramListUpdated(any()); 236 verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 237 verify(mAidlTunerCallbackMocks[3], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 238 } 239 240 @Test testClientClosing()241 public void testClientClosing() throws RemoteException { 242 // Open 2 clients that use different filters that are both sensitive to TEST_AM_FM_ID. 243 openAidlClients(2); 244 ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(), 245 new HashSet<ProgramSelector.Identifier>(Arrays.asList(TEST_AM_FM_ID)), true, 246 false); 247 ProgramList.Filter typeFilter = new ProgramList.Filter( 248 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 249 new HashSet<ProgramSelector.Identifier>(), true, false); 250 251 // Start updates on the clients, and verify the HAL filter is updated after each one. 252 mTunerSessions[0].startProgramListUpdates(idFilter); 253 ProgramFilter halFilter = Convert.programFilterToHal(idFilter); 254 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 255 256 mTunerSessions[1].startProgramListUpdates(typeFilter); 257 halFilter.identifiers.clear(); 258 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 259 260 // Update the HAL with TEST_AM_FM_INFO, and verify both clients are updated. 261 updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_INFO), null); 262 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, 263 Arrays.asList(TEST_AM_FM_INFO), null); 264 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, 265 Arrays.asList(TEST_AM_FM_INFO), null); 266 267 // Stop updates on the first client and verify the HAL filter is updated. 268 mTunerSessions[0].stopProgramListUpdates(); 269 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(Convert.programFilterToHal( 270 typeFilter)); 271 272 // Update the HAL with TEST_AM_FM_MODIFIED_INFO, and verify only the remaining client is 273 // updated. 274 updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 275 verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 276 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, 277 Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 278 279 // Close the other client without explicitly stopping updates, and verify HAL updates are 280 // stopped as well. 281 mTunerSessions[1].close(); 282 verify(mHalTunerSessionMock).stopProgramListUpdates(); 283 } 284 285 @Test testNullAidlFilter()286 public void testNullAidlFilter() throws RemoteException { 287 openAidlClients(1); 288 mTunerSessions[0].startProgramListUpdates(null); 289 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(any()); 290 291 // Verify the AIDL client receives all types of updates (e.g. a new program, an update to 292 // that program, and a category). 293 updateHalProgramInfo(true, Arrays.asList(TEST_AM_FM_INFO, TEST_RDS_INFO), null); 294 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList( 295 TEST_AM_FM_INFO, TEST_RDS_INFO), null); 296 updateHalProgramInfo(false, Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 297 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 298 Arrays.asList(TEST_AM_FM_MODIFIED_INFO), null); 299 updateHalProgramInfo(false, Arrays.asList(TEST_DAB_INFO), null); 300 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 301 Arrays.asList(TEST_DAB_INFO), null); 302 303 // Verify closing the AIDL session also stops HAL updates. 304 mTunerSessions[0].close(); 305 verify(mHalTunerSessionMock).stopProgramListUpdates(); 306 } 307 openAidlClients(int numClients)308 private void openAidlClients(int numClients) throws RemoteException { 309 mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; 310 mTunerSessions = new TunerSession[numClients]; 311 for (int i = 0; i < numClients; i++) { 312 mAidlTunerCallbackMocks[i] = mock(android.hardware.radio.ITunerCallback.class); 313 mTunerSessions[i] = mRadioModule.openSession(mAidlTunerCallbackMocks[i]); 314 } 315 } 316 updateHalProgramInfo(boolean purge, List<RadioManager.ProgramInfo> modified, List<ProgramSelector.Identifier> removed)317 private void updateHalProgramInfo(boolean purge, List<RadioManager.ProgramInfo> modified, 318 List<ProgramSelector.Identifier> removed) throws RemoteException { 319 ProgramListChunk programListChunk = new ProgramListChunk(); 320 programListChunk.purge = purge; 321 programListChunk.complete = true; 322 if (modified != null) { 323 for (RadioManager.ProgramInfo mod : modified) { 324 programListChunk.modified.add(TestUtils.programInfoToHal(mod)); 325 } 326 } 327 if (removed != null) { 328 for (ProgramSelector.Identifier id : removed) { 329 programListChunk.removed.add(Convert.programIdentifierToHal(id)); 330 } 331 } 332 mHalTunerCallback.onProgramListUpdated(programListChunk); 333 } 334 verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock, boolean purge, List<RadioManager.ProgramInfo> modified, List<UniqueProgramIdentifier> removed)335 private void verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock, 336 boolean purge, List<RadioManager.ProgramInfo> modified, 337 List<UniqueProgramIdentifier> removed) throws RemoteException { 338 HashSet<RadioManager.ProgramInfo> modifiedSet = new HashSet<>(); 339 if (modified != null) { 340 modifiedSet.addAll(modified); 341 } 342 HashSet<UniqueProgramIdentifier> removedSet = new HashSet<>(); 343 if (removed != null) { 344 removedSet.addAll(removed); 345 } 346 ProgramList.Chunk expectedChunk = new ProgramList.Chunk(purge, true, modifiedSet, 347 removedSet); 348 verify(clientMock, CB_TIMEOUT).onProgramListUpdated(expectedChunk); 349 } 350 } 351