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.os.RemoteException; 39 40 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; 41 import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; 42 import com.android.server.broadcastradio.RadioServiceUserController; 43 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.mockito.Mock; 47 import org.mockito.stubbing.Answer; 48 import org.mockito.verification.VerificationWithTimeout; 49 50 import java.util.Arrays; 51 import java.util.HashSet; 52 import java.util.List; 53 54 /** 55 * Tests for v2 HAL RadioModule. 56 */ 57 public class StartProgramListUpdatesFanoutTest extends ExtendedRadioMockitoTestCase { 58 private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout"; 59 60 private static final VerificationWithTimeout CB_TIMEOUT = timeout(500); 61 62 // Mocks 63 @Mock IBroadcastRadio mBroadcastRadioMock; 64 @Mock ITunerSession mHalTunerSessionMock; 65 private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks; 66 67 // RadioModule under test 68 private RadioModule mRadioModule; 69 70 // Objects created by mRadioModule 71 private ITunerCallback mHalTunerCallback; 72 private TunerSession[] mTunerSessions; 73 74 // Data objects used during tests 75 private final ProgramSelector.Identifier mAmFmIdentifier = 76 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, 88500); 77 private final RadioManager.ProgramInfo mAmFmInfo = TestUtils.makeProgramInfo( 78 ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 0); 79 private final RadioManager.ProgramInfo mModifiedAmFmInfo = TestUtils.makeProgramInfo( 80 ProgramSelector.PROGRAM_TYPE_FM, mAmFmIdentifier, 1); 81 82 private final ProgramSelector.Identifier mRdsIdentifier = 83 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019); 84 private final RadioManager.ProgramInfo mRdsInfo = TestUtils.makeProgramInfo( 85 ProgramSelector.PROGRAM_TYPE_FM, mRdsIdentifier, 0); 86 87 private final ProgramSelector.Identifier mDabEnsembleIdentifier = 88 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, 1337); 89 private final RadioManager.ProgramInfo mDabEnsembleInfo = TestUtils.makeProgramInfo( 90 ProgramSelector.PROGRAM_TYPE_DAB, mDabEnsembleIdentifier, 0); 91 92 @Override initializeSession(StaticMockitoSessionBuilder builder)93 protected void initializeSession(StaticMockitoSessionBuilder builder) { 94 builder.spyStatic(RadioServiceUserController.class); 95 } 96 97 @Before setup()98 public void setup() throws RemoteException { 99 doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); 100 101 mRadioModule = new RadioModule(mBroadcastRadioMock, 102 TestUtils.makeDefaultModuleProperties()); 103 104 doAnswer((Answer) invocation -> { 105 mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; 106 IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback) 107 invocation.getArguments()[1]; 108 cb.onValues(Result.OK, mHalTunerSessionMock); 109 return null; 110 }).when(mBroadcastRadioMock).openSession(any(), any()); 111 when(mHalTunerSessionMock.startProgramListUpdates(any())).thenReturn(Result.OK); 112 } 113 114 @Test testFanout()115 public void testFanout() throws RemoteException { 116 // Open 3 clients that will all use the same filter, and start updates on two of them for 117 // now. The HAL TunerSession should only see 1 filter update. 118 openAidlClients(3); 119 ProgramList.Filter aidlFilter = new ProgramList.Filter(new HashSet<Integer>(), 120 new HashSet<ProgramSelector.Identifier>(), true, false); 121 ProgramFilter halFilter = Convert.programFilterToHal(aidlFilter); 122 for (int i = 0; i < 2; i++) { 123 mTunerSessions[i].startProgramListUpdates(aidlFilter); 124 } 125 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 126 127 // Initiate a program list update from the HAL side and verify both connected AIDL clients 128 // receive the update. 129 updateHalProgramInfo(true, Arrays.asList(mAmFmInfo, mRdsInfo), null); 130 for (int i = 0; i < 2; i++) { 131 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[i], true, Arrays.asList( 132 mAmFmInfo, mRdsInfo), null); 133 } 134 135 // Repeat with a non-purging update. 136 updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), 137 Arrays.asList(mRdsIdentifier)); 138 for (int i = 0; i < 2; i++) { 139 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[i], false, 140 Arrays.asList(mModifiedAmFmInfo), Arrays.asList(mRdsIdentifier)); 141 } 142 143 // Now start updates on the 3rd client. Verify the HAL function has not been called again 144 // and client receives the appropriate update. 145 mTunerSessions[2].startProgramListUpdates(aidlFilter); 146 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(any()); 147 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], true, 148 Arrays.asList(mModifiedAmFmInfo), null); 149 } 150 151 @Test testFiltering()152 public void testFiltering() throws RemoteException { 153 // Open 4 clients that will use the following filters: 154 // [0]: ID mRdsIdentifier, modifications excluded 155 // [1]: No categories, modifications excluded 156 // [2]: Type IDENTIFIER_TYPE_AMFM_FREQUENCY, modifications excluded 157 // [3]: Type IDENTIFIER_TYPE_AMFM_FREQUENCY, modifications included 158 openAidlClients(4); 159 ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(), 160 new HashSet<ProgramSelector.Identifier>(Arrays.asList(mRdsIdentifier)), true, true); 161 ProgramList.Filter categoryFilter = new ProgramList.Filter(new HashSet<Integer>(), 162 new HashSet<ProgramSelector.Identifier>(), false, true); 163 ProgramList.Filter typeFilterWithoutModifications = new ProgramList.Filter( 164 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 165 new HashSet<ProgramSelector.Identifier>(), true, true); 166 ProgramList.Filter typeFilterWithModifications = new ProgramList.Filter( 167 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 168 new HashSet<ProgramSelector.Identifier>(), true, false); 169 170 // Start updates on the clients in order. The HAL filter should get updated after each 171 // client except [2]. Client [2] should update received chunk with an empty program 172 // list 173 mTunerSessions[0].startProgramListUpdates(idFilter); 174 ProgramFilter halFilter = Convert.programFilterToHal(idFilter); 175 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 176 177 mTunerSessions[1].startProgramListUpdates(categoryFilter); 178 halFilter.identifiers.clear(); 179 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 180 181 mTunerSessions[2].startProgramListUpdates(typeFilterWithoutModifications); 182 verify(mHalTunerSessionMock, times(2)).startProgramListUpdates(any()); 183 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], true, Arrays.asList(), 184 null); 185 verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 186 187 mTunerSessions[3].startProgramListUpdates(typeFilterWithModifications); 188 halFilter.excludeModifications = false; 189 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 190 191 // Adding mRdsInfo should update clients [0] and [1]. 192 updateHalProgramInfo(false, Arrays.asList(mRdsInfo), null); 193 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, Arrays.asList(mRdsInfo), 194 null); 195 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, Arrays.asList(mRdsInfo), 196 null); 197 198 // Adding mAmFmInfo should update clients [1], [2], and [3]. 199 updateHalProgramInfo(false, Arrays.asList(mAmFmInfo), null); 200 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, Arrays.asList(mAmFmInfo), 201 null); 202 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], false, Arrays.asList(mAmFmInfo), 203 null); 204 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, Arrays.asList(mAmFmInfo), 205 null); 206 207 // Modifying mAmFmInfo to mModifiedAmFmInfo should update only [3]. 208 updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), null); 209 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, 210 Arrays.asList(mModifiedAmFmInfo), null); 211 212 // Adding mDabEnsembleInfo should not update any client. 213 updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null); 214 verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 215 verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 216 verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 217 verify(mAidlTunerCallbackMocks[3], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 218 } 219 220 @Test testClientClosing()221 public void testClientClosing() throws RemoteException { 222 // Open 2 clients that use different filters that are both sensitive to mAmFmIdentifier. 223 openAidlClients(2); 224 ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(), 225 new HashSet<ProgramSelector.Identifier>(Arrays.asList(mAmFmIdentifier)), true, 226 false); 227 ProgramList.Filter typeFilter = new ProgramList.Filter( 228 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 229 new HashSet<ProgramSelector.Identifier>(), true, false); 230 231 // Start updates on the clients, and verify the HAL filter is updated after each one. 232 mTunerSessions[0].startProgramListUpdates(idFilter); 233 ProgramFilter halFilter = Convert.programFilterToHal(idFilter); 234 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 235 236 mTunerSessions[1].startProgramListUpdates(typeFilter); 237 halFilter.identifiers.clear(); 238 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 239 240 // Update the HAL with mAmFmInfo, and verify both clients are updated. 241 updateHalProgramInfo(true, Arrays.asList(mAmFmInfo), null); 242 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList(mAmFmInfo), 243 null); 244 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, Arrays.asList(mAmFmInfo), 245 null); 246 247 // Stop updates on the first client and verify the HAL filter is updated. 248 mTunerSessions[0].stopProgramListUpdates(); 249 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(Convert.programFilterToHal( 250 typeFilter)); 251 252 // Update the HAL with mModifiedAmFmInfo, and verify only the remaining client is updated. 253 updateHalProgramInfo(true, Arrays.asList(mModifiedAmFmInfo), null); 254 verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 255 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, 256 Arrays.asList(mModifiedAmFmInfo), null); 257 258 // Close the other client without explicitly stopping updates, and verify HAL updates are 259 // stopped as well. 260 mTunerSessions[1].close(); 261 verify(mHalTunerSessionMock).stopProgramListUpdates(); 262 } 263 264 @Test testNullAidlFilter()265 public void testNullAidlFilter() throws RemoteException { 266 openAidlClients(1); 267 mTunerSessions[0].startProgramListUpdates(null); 268 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(any()); 269 270 // Verify the AIDL client receives all types of updates (e.g. a new program, an update to 271 // that program, and a category). 272 updateHalProgramInfo(true, Arrays.asList(mAmFmInfo, mRdsInfo), null); 273 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList( 274 mAmFmInfo, mRdsInfo), null); 275 updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), null); 276 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 277 Arrays.asList(mModifiedAmFmInfo), null); 278 updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null); 279 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 280 Arrays.asList(mDabEnsembleInfo), null); 281 282 // Verify closing the AIDL session also stops HAL updates. 283 mTunerSessions[0].close(); 284 verify(mHalTunerSessionMock).stopProgramListUpdates(); 285 } 286 openAidlClients(int numClients)287 private void openAidlClients(int numClients) throws RemoteException { 288 mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; 289 mTunerSessions = new TunerSession[numClients]; 290 for (int i = 0; i < numClients; i++) { 291 mAidlTunerCallbackMocks[i] = mock(android.hardware.radio.ITunerCallback.class); 292 mTunerSessions[i] = mRadioModule.openSession(mAidlTunerCallbackMocks[i]); 293 } 294 } 295 updateHalProgramInfo(boolean purge, List<RadioManager.ProgramInfo> modified, List<ProgramSelector.Identifier> removed)296 private void updateHalProgramInfo(boolean purge, List<RadioManager.ProgramInfo> modified, 297 List<ProgramSelector.Identifier> removed) throws RemoteException { 298 ProgramListChunk programListChunk = new ProgramListChunk(); 299 programListChunk.purge = purge; 300 programListChunk.complete = true; 301 if (modified != null) { 302 for (RadioManager.ProgramInfo mod : modified) { 303 programListChunk.modified.add(TestUtils.programInfoToHal(mod)); 304 } 305 } 306 if (removed != null) { 307 for (ProgramSelector.Identifier id : removed) { 308 programListChunk.removed.add(Convert.programIdentifierToHal(id)); 309 } 310 } 311 mHalTunerCallback.onProgramListUpdated(programListChunk); 312 } 313 verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock, boolean purge, List<RadioManager.ProgramInfo> modified, List<ProgramSelector.Identifier> removed)314 private void verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock, 315 boolean purge, List<RadioManager.ProgramInfo> modified, 316 List<ProgramSelector.Identifier> removed) throws RemoteException { 317 HashSet<RadioManager.ProgramInfo> modifiedSet = new HashSet<>(); 318 if (modified != null) { 319 modifiedSet.addAll(modified); 320 } 321 HashSet<ProgramSelector.Identifier> removedSet = new HashSet<>(); 322 if (removed != null) { 323 removedSet.addAll(removed); 324 } 325 ProgramList.Chunk expectedChunk = new ProgramList.Chunk(purge, true, modifiedSet, 326 removedSet); 327 verify(clientMock, CB_TIMEOUT).onProgramListUpdated(expectedChunk); 328 } 329 } 330