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