• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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