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