• 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     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