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]. 170 mTunerSessions[0].startProgramListUpdates(idFilter); 171 ProgramFilter halFilter = Convert.programFilterToHal(idFilter); 172 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 173 174 mTunerSessions[1].startProgramListUpdates(categoryFilter); 175 halFilter.identifiers.clear(); 176 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 177 178 mTunerSessions[2].startProgramListUpdates(typeFilterWithoutModifications); 179 verify(mHalTunerSessionMock, times(2)).startProgramListUpdates(any()); 180 181 mTunerSessions[3].startProgramListUpdates(typeFilterWithModifications); 182 halFilter.excludeModifications = false; 183 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 184 185 // Adding mRdsInfo should update clients [0] and [1]. 186 updateHalProgramInfo(false, Arrays.asList(mRdsInfo), null); 187 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, Arrays.asList(mRdsInfo), 188 null); 189 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, Arrays.asList(mRdsInfo), 190 null); 191 192 // Adding mAmFmInfo should update clients [1], [2], and [3]. 193 updateHalProgramInfo(false, Arrays.asList(mAmFmInfo), null); 194 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], false, Arrays.asList(mAmFmInfo), 195 null); 196 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[2], false, Arrays.asList(mAmFmInfo), 197 null); 198 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, Arrays.asList(mAmFmInfo), 199 null); 200 201 // Modifying mAmFmInfo to mModifiedAmFmInfo should update only [3]. 202 updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), null); 203 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[3], false, 204 Arrays.asList(mModifiedAmFmInfo), null); 205 206 // Adding mDabEnsembleInfo should not update any client. 207 updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null); 208 verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 209 verify(mAidlTunerCallbackMocks[1], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 210 verify(mAidlTunerCallbackMocks[2], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 211 verify(mAidlTunerCallbackMocks[3], CB_TIMEOUT.times(2)).onProgramListUpdated(any()); 212 } 213 214 @Test testClientClosing()215 public void testClientClosing() throws RemoteException { 216 // Open 2 clients that use different filters that are both sensitive to mAmFmIdentifier. 217 openAidlClients(2); 218 ProgramList.Filter idFilter = new ProgramList.Filter(new HashSet<Integer>(), 219 new HashSet<ProgramSelector.Identifier>(Arrays.asList(mAmFmIdentifier)), true, 220 false); 221 ProgramList.Filter typeFilter = new ProgramList.Filter( 222 new HashSet<Integer>(Arrays.asList(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)), 223 new HashSet<ProgramSelector.Identifier>(), true, false); 224 225 // Start updates on the clients, and verify the HAL filter is updated after each one. 226 mTunerSessions[0].startProgramListUpdates(idFilter); 227 ProgramFilter halFilter = Convert.programFilterToHal(idFilter); 228 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 229 230 mTunerSessions[1].startProgramListUpdates(typeFilter); 231 halFilter.identifiers.clear(); 232 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(halFilter); 233 234 // Update the HAL with mAmFmInfo, and verify both clients are updated. 235 updateHalProgramInfo(true, Arrays.asList(mAmFmInfo), null); 236 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList(mAmFmInfo), 237 null); 238 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, Arrays.asList(mAmFmInfo), 239 null); 240 241 // Stop updates on the first client and verify the HAL filter is updated. 242 mTunerSessions[0].stopProgramListUpdates(); 243 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(Convert.programFilterToHal( 244 typeFilter)); 245 246 // Update the HAL with mModifiedAmFmInfo, and verify only the remaining client is updated. 247 updateHalProgramInfo(true, Arrays.asList(mModifiedAmFmInfo), null); 248 verify(mAidlTunerCallbackMocks[0], CB_TIMEOUT.times(1)).onProgramListUpdated(any()); 249 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[1], true, 250 Arrays.asList(mModifiedAmFmInfo), null); 251 252 // Close the other client without explicitly stopping updates, and verify HAL updates are 253 // stopped as well. 254 mTunerSessions[1].close(); 255 verify(mHalTunerSessionMock).stopProgramListUpdates(); 256 } 257 258 @Test testNullAidlFilter()259 public void testNullAidlFilter() throws RemoteException { 260 openAidlClients(1); 261 mTunerSessions[0].startProgramListUpdates(null); 262 verify(mHalTunerSessionMock, times(1)).startProgramListUpdates(any()); 263 264 // Verify the AIDL client receives all types of updates (e.g. a new program, an update to 265 // that program, and a category). 266 updateHalProgramInfo(true, Arrays.asList(mAmFmInfo, mRdsInfo), null); 267 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], true, Arrays.asList( 268 mAmFmInfo, mRdsInfo), null); 269 updateHalProgramInfo(false, Arrays.asList(mModifiedAmFmInfo), null); 270 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 271 Arrays.asList(mModifiedAmFmInfo), null); 272 updateHalProgramInfo(false, Arrays.asList(mDabEnsembleInfo), null); 273 verifyAidlClientReceivedChunk(mAidlTunerCallbackMocks[0], false, 274 Arrays.asList(mDabEnsembleInfo), null); 275 276 // Verify closing the AIDL session also stops HAL updates. 277 mTunerSessions[0].close(); 278 verify(mHalTunerSessionMock).stopProgramListUpdates(); 279 } 280 openAidlClients(int numClients)281 private void openAidlClients(int numClients) throws RemoteException { 282 mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; 283 mTunerSessions = new TunerSession[numClients]; 284 for (int i = 0; i < numClients; i++) { 285 mAidlTunerCallbackMocks[i] = mock(android.hardware.radio.ITunerCallback.class); 286 mTunerSessions[i] = mRadioModule.openSession(mAidlTunerCallbackMocks[i]); 287 } 288 } 289 updateHalProgramInfo(boolean purge, List<RadioManager.ProgramInfo> modified, List<ProgramSelector.Identifier> removed)290 private void updateHalProgramInfo(boolean purge, List<RadioManager.ProgramInfo> modified, 291 List<ProgramSelector.Identifier> removed) throws RemoteException { 292 ProgramListChunk programListChunk = new ProgramListChunk(); 293 programListChunk.purge = purge; 294 programListChunk.complete = true; 295 if (modified != null) { 296 for (RadioManager.ProgramInfo mod : modified) { 297 programListChunk.modified.add(TestUtils.programInfoToHal(mod)); 298 } 299 } 300 if (removed != null) { 301 for (ProgramSelector.Identifier id : removed) { 302 programListChunk.removed.add(Convert.programIdentifierToHal(id)); 303 } 304 } 305 mHalTunerCallback.onProgramListUpdated(programListChunk); 306 } 307 verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock, boolean purge, List<RadioManager.ProgramInfo> modified, List<ProgramSelector.Identifier> removed)308 private void verifyAidlClientReceivedChunk(android.hardware.radio.ITunerCallback clientMock, 309 boolean purge, List<RadioManager.ProgramInfo> modified, 310 List<ProgramSelector.Identifier> removed) throws RemoteException { 311 HashSet<RadioManager.ProgramInfo> modifiedSet = new HashSet<>(); 312 if (modified != null) { 313 modifiedSet.addAll(modified); 314 } 315 HashSet<ProgramSelector.Identifier> removedSet = new HashSet<>(); 316 if (removed != null) { 317 removedSet.addAll(removed); 318 } 319 ProgramList.Chunk expectedChunk = new ProgramList.Chunk(purge, true, modifiedSet, 320 removedSet); 321 verify(clientMock, CB_TIMEOUT).onProgramListUpdated(expectedChunk); 322 } 323 } 324