1 /*
2  * Copyright (C) 2022 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 
17 package com.android.server.broadcastradio.aidl;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
20 
21 import static com.google.common.truth.Truth.assertWithMessage;
22 
23 import static org.junit.Assert.assertThrows;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyBoolean;
26 import static org.mockito.ArgumentMatchers.anyInt;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.Mockito.doAnswer;
29 import static org.mockito.Mockito.doThrow;
30 import static org.mockito.Mockito.mock;
31 import static org.mockito.Mockito.never;
32 import static org.mockito.Mockito.timeout;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 
36 import android.app.compat.CompatChanges;
37 import android.graphics.Bitmap;
38 import android.hardware.broadcastradio.IBroadcastRadio;
39 import android.hardware.broadcastradio.ITunerCallback;
40 import android.hardware.broadcastradio.IdentifierType;
41 import android.hardware.broadcastradio.ProgramFilter;
42 import android.hardware.broadcastradio.ProgramInfo;
43 import android.hardware.broadcastradio.ProgramListChunk;
44 import android.hardware.broadcastradio.Result;
45 import android.hardware.broadcastradio.VendorKeyValue;
46 import android.hardware.radio.ProgramList;
47 import android.hardware.radio.ProgramSelector;
48 import android.hardware.radio.RadioManager;
49 import android.hardware.radio.RadioTuner;
50 import android.os.Binder;
51 import android.os.ParcelableException;
52 import android.os.RemoteException;
53 import android.os.ServiceSpecificException;
54 import android.os.UserHandle;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 
58 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
59 import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
60 import com.android.server.broadcastradio.RadioServiceUserController;
61 
62 import org.junit.After;
63 import org.junit.Before;
64 import org.junit.Test;
65 import org.mockito.Mock;
66 import org.mockito.verification.VerificationWithTimeout;
67 
68 import java.util.ArrayList;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Set;
72 
73 /**
74  * Tests for AIDL HAL TunerSession.
75  */
76 public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
77 
78     private static final int USER_ID_1 = 11;
79     private static final int USER_ID_2 = 12;
80     private static final VerificationWithTimeout CALLBACK_TIMEOUT =
81             timeout(/* millis= */ 200);
82     private static final int SIGNAL_QUALITY = 90;
83     private static final long AM_FM_FREQUENCY_SPACING = 500;
84     private static final long[] AM_FM_FREQUENCY_LIST = {97_500, 98_100, 99_100};
85     private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
86             new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
87                     /* lowerLimit= */ 87_500, /* upperLimit= */ 108_000, /* spacing= */ 100,
88                     /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
89                     /* ea= */ false);
90     private static final RadioManager.BandConfig FM_BAND_CONFIG =
91             new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
92     private static final int UNSUPPORTED_CONFIG_FLAG = 0;
93 
94     private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
95             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
96                     /* value= */ 88_500);
97     private static final ProgramSelector.Identifier TEST_RDS_PI_ID =
98             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI,
99                     /* value= */ 15_019);
100 
101     private static final RadioManager.ProgramInfo TEST_FM_INFO = AidlTestUtils.makeProgramInfo(
102             AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
103                     TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID, TEST_FM_FREQUENCY_ID,
104             SIGNAL_QUALITY);
105     private static final RadioManager.ProgramInfo TEST_FM_INFO_MODIFIED =
106             AidlTestUtils.makeProgramInfo(AidlTestUtils.makeProgramSelector(
107                     ProgramSelector.PROGRAM_TYPE_FM, TEST_FM_FREQUENCY_ID), TEST_FM_FREQUENCY_ID,
108                     TEST_FM_FREQUENCY_ID, /* signalQuality= */ 100);
109     private static final RadioManager.ProgramInfo TEST_RDS_INFO = AidlTestUtils.makeProgramInfo(
110             AidlTestUtils.makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, TEST_RDS_PI_ID),
111             TEST_RDS_PI_ID, new ProgramSelector.Identifier(
112                     ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 89_500),
113             SIGNAL_QUALITY);
114 
115     // Mocks
116     @Mock
117     private UserHandle mUserHandleMock;
118     @Mock
119     private IBroadcastRadio mBroadcastRadioMock;
120     private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
121 
122     // RadioModule under test
123     private RadioModule mRadioModule;
124 
125     // Objects created by mRadioModule
126     private ITunerCallback mHalTunerCallback;
127     private ProgramInfo mHalCurrentInfo;
128     private final ArrayMap<Integer, Boolean> mHalConfigMap = new ArrayMap<>();
129 
130     private TunerSession[] mTunerSessions;
131 
132     @Override
initializeSession(StaticMockitoSessionBuilder builder)133     protected void initializeSession(StaticMockitoSessionBuilder builder) {
134         builder.spyStatic(RadioServiceUserController.class).spyStatic(CompatChanges.class)
135                 .spyStatic(Binder.class);
136     }
137 
138     @Before
setup()139     public void setup() throws Exception {
140         when(mUserHandleMock.getIdentifier()).thenReturn(USER_ID_1);
141         doReturn(true).when(() -> CompatChanges.isChangeEnabled(
142                 eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
143         doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
144         doReturn(USER_ID_1).when(() -> RadioServiceUserController.getCurrentUser());
145         doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
146 
147         mRadioModule = new RadioModule(mBroadcastRadioMock,
148                 AidlTestUtils.makeDefaultModuleProperties());
149 
150         doAnswer(invocation -> {
151             mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
152             return null;
153         }).when(mBroadcastRadioMock).setTunerCallback(any());
154         mRadioModule.setInternalHalCallback();
155 
156         doAnswer(invocation -> {
157             android.hardware.broadcastradio.ProgramSelector halSel =
158                     (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
159             mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
160             if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
161                 throw new ServiceSpecificException(Result.NOT_SUPPORTED);
162             }
163             mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
164             return Result.OK;
165         }).when(mBroadcastRadioMock).tune(any());
166 
167         doAnswer(invocation -> {
168             if ((boolean) invocation.getArguments()[0]) {
169                 mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
170             } else {
171                 mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
172             }
173             mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
174             mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
175             mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
176             return Result.OK;
177         }).when(mBroadcastRadioMock).step(anyBoolean());
178 
179         doAnswer(invocation -> {
180             if (mHalCurrentInfo == null) {
181                 android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
182                         AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
183 
184                 mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
185                 return Result.OK;
186             }
187             mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
188                     mHalCurrentInfo.selector.primaryId.value,
189                     !(boolean) invocation.getArguments()[0]);
190             mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
191             mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
192             mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
193             return Result.OK;
194         }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
195 
196         when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
197 
198         doAnswer(invocation -> {
199             int configFlag = (int) invocation.getArguments()[0];
200             if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
201                 throw new ServiceSpecificException(Result.NOT_SUPPORTED);
202             }
203             return mHalConfigMap.getOrDefault(configFlag, false);
204         }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
205 
206         doAnswer(invocation -> {
207             int configFlag = (int) invocation.getArguments()[0];
208             if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
209                 throw new ServiceSpecificException(Result.NOT_SUPPORTED);
210             }
211             mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
212             return null;
213         }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
214     }
215 
216     @After
cleanUp()217     public void cleanUp() {
218         mHalConfigMap.clear();
219     }
220 
221     @Test
openSession_withMultipleSessions()222     public void openSession_withMultipleSessions() throws Exception {
223         int numSessions = 3;
224 
225         openAidlClients(numSessions);
226 
227         for (int index = 0; index < numSessions; index++) {
228             assertWithMessage("Session of index %s close state", index)
229                     .that(mTunerSessions[index].isClosed()).isFalse();
230         }
231     }
232 
233     @Test
setConfiguration()234     public void setConfiguration() throws Exception {
235         openAidlClients(/* numClients= */ 1);
236 
237         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
238 
239         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
240     }
241 
242     @Test
setConfiguration_forNonCurrentUser_doesNotInvokesCallback()243     public void setConfiguration_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
244         openAidlClients(/* numClients= */ 1);
245         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
246 
247         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
248 
249         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
250                 .onConfigurationChanged(FM_BAND_CONFIG);
251     }
252 
253     @Test
getConfiguration()254     public void getConfiguration() throws Exception {
255         openAidlClients(/* numClients= */ 1);
256         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
257 
258         RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
259 
260         assertWithMessage("Session configuration").that(config)
261                 .isEqualTo(FM_BAND_CONFIG);
262     }
263 
264     @Test
setMuted_withUnmuted()265     public void setMuted_withUnmuted() throws Exception {
266         openAidlClients(/* numClients= */ 1);
267 
268         mTunerSessions[0].setMuted(/* mute= */ false);
269 
270         assertWithMessage("Session mute state after setting unmuted")
271                 .that(mTunerSessions[0].isMuted()).isFalse();
272     }
273 
274     @Test
setMuted_withMuted()275     public void setMuted_withMuted() throws Exception {
276         openAidlClients(/* numClients= */ 1);
277 
278         mTunerSessions[0].setMuted(/* mute= */ true);
279 
280         assertWithMessage("Session mute state after setting muted")
281                 .that(mTunerSessions[0].isMuted()).isTrue();
282     }
283 
284     @Test
close_withOneSession()285     public void close_withOneSession() throws Exception {
286         openAidlClients(/* numClients= */ 1);
287 
288         mTunerSessions[0].close();
289 
290         assertWithMessage("Close state of broadcast radio service session")
291                 .that(mTunerSessions[0].isClosed()).isTrue();
292     }
293 
294     @Test
close_withOnlyOneSession_withMultipleSessions()295     public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
296         int numSessions = 3;
297         openAidlClients(numSessions);
298         int closeIdx = 0;
299 
300         mTunerSessions[closeIdx].close();
301 
302         for (int index = 0; index < numSessions; index++) {
303             if (index == closeIdx) {
304                 assertWithMessage(
305                         "Close state of broadcast radio service session of index %s", index)
306                         .that(mTunerSessions[index].isClosed()).isTrue();
307             } else {
308                 assertWithMessage(
309                         "Close state of broadcast radio service session of index %s", index)
310                         .that(mTunerSessions[index].isClosed()).isFalse();
311             }
312         }
313     }
314 
315     @Test
close_withOneSession_withError()316     public void close_withOneSession_withError() throws Exception {
317         openAidlClients(/* numClients= */ 1);
318         int errorCode = RadioTuner.ERROR_SERVER_DIED;
319 
320         mTunerSessions[0].close(errorCode);
321 
322         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
323         assertWithMessage("Close state of broadcast radio service session")
324                 .that(mTunerSessions[0].isClosed()).isTrue();
325     }
326 
327     @Test
closeSessions_withMultipleSessions_withError()328     public void closeSessions_withMultipleSessions_withError() throws Exception {
329         int numSessions = 3;
330         openAidlClients(numSessions);
331 
332         int errorCode = RadioTuner.ERROR_SERVER_DIED;
333         mRadioModule.closeSessions(errorCode);
334 
335         for (int index = 0; index < numSessions; index++) {
336             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
337             assertWithMessage("Close state of broadcast radio service session of index %s", index)
338                     .that(mTunerSessions[index].isClosed()).isTrue();
339         }
340     }
341 
342     @Test
tune_withOneSession()343     public void tune_withOneSession() throws Exception {
344         openAidlClients(/* numClients= */ 1);
345         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
346         RadioManager.ProgramInfo tuneInfo =
347                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
348 
349         mTunerSessions[0].tune(initialSel);
350 
351         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
352     }
353 
354     @Test
tune_withLowerSdkVersion()355     public void tune_withLowerSdkVersion() throws Exception {
356         doReturn(false).when(() -> CompatChanges.isChangeEnabled(
357                 eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
358         openAidlClients(/* numClients= */ 1);
359         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
360         RadioManager.ProgramInfo tuneInfo =
361                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
362 
363         mTunerSessions[0].tune(initialSel);
364 
365         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
366     }
367 
368     @Test
tune_withMultipleSessions()369     public void tune_withMultipleSessions() throws Exception {
370         int numSessions = 3;
371         openAidlClients(numSessions);
372         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
373         RadioManager.ProgramInfo tuneInfo =
374                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
375 
376         mTunerSessions[0].tune(initialSel);
377 
378         for (int index = 0; index < numSessions; index++) {
379             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
380                     .onCurrentProgramInfoChanged(tuneInfo);
381         }
382     }
383 
384     @Test
tune_withUnsupportedSelector_throwsException()385     public void tune_withUnsupportedSelector_throwsException() throws Exception {
386         ProgramSelector.Identifier dabPrimaryId =
387                 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
388                         /* value= */ 0xA000000111L);
389         ProgramSelector.Identifier[] dabSecondaryIds =  new ProgramSelector.Identifier[]{
390                 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
391                         /* value= */ 1337),
392                 new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
393                         /* value= */ 225648)};
394         ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
395                 dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
396         openAidlClients(/* numClients= */ 1);
397 
398         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
399                 () -> mTunerSessions[0].tune(unsupportedSelector));
400 
401         assertWithMessage("Exception for tuning on unsupported program selector")
402                 .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
403     }
404 
405     @Test
tune_withInvalidSelector_throwsIllegalArgumentException()406     public void tune_withInvalidSelector_throwsIllegalArgumentException() throws Exception {
407         openAidlClients(/* numClients= */ 1);
408         ProgramSelector.Identifier invalidDabId = new ProgramSelector.Identifier(
409                 ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, /* value= */ 0x1001);
410         ProgramSelector invalidSel = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
411                 invalidDabId, new ProgramSelector.Identifier[0], new long[0]);
412 
413         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
414                 () -> mTunerSessions[0].tune(invalidSel));
415 
416         assertWithMessage("Exception for tuning on DAB selector without DAB_SID_EXT primary id")
417                 .that(thrown).hasMessageThat().contains("tune: INVALID_ARGUMENTS");
418     }
419 
420     @Test
tune_forNonCurrentUser_doesNotTune()421     public void tune_forNonCurrentUser_doesNotTune() throws Exception {
422         openAidlClients(/* numClients= */ 1);
423         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
424         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
425         RadioManager.ProgramInfo tuneInfo =
426                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
427 
428         mTunerSessions[0].tune(initialSel);
429 
430         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
431                 .onCurrentProgramInfoChanged(tuneInfo);
432     }
433 
434     @Test
tune_forSystemUser()435     public void tune_forSystemUser() throws Exception {
436         when(mUserHandleMock.getIdentifier()).thenReturn(UserHandle.USER_SYSTEM);
437         doReturn(mUserHandleMock).when(() -> Binder.getCallingUserHandle());
438         doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
439         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
440         RadioManager.ProgramInfo tuneInfo =
441                 AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
442         openAidlClients(/* numClients= */ 1);
443 
444         mTunerSessions[0].tune(initialSel);
445 
446         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
447     }
448 
449     @Test
tune_withUnknownErrorFromHal_fails()450     public void tune_withUnknownErrorFromHal_fails() throws Exception {
451         openAidlClients(/* numClients= */ 1);
452         ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
453         doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
454                 .when(mBroadcastRadioMock).tune(any());
455 
456         ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
457             mTunerSessions[0].tune(sel);
458         });
459 
460         assertWithMessage("Unknown error HAL exception when tuning")
461                 .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
462     }
463 
464     @Test
step_withDirectionUp()465     public void step_withDirectionUp() throws Exception {
466         long initFreq = AM_FM_FREQUENCY_LIST[1];
467         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
468         RadioManager.ProgramInfo stepUpInfo = AidlTestUtils.makeProgramInfo(
469                 AidlTestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING),
470                 SIGNAL_QUALITY);
471         openAidlClients(/* numClients= */ 1);
472         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
473                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
474 
475         mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
476 
477         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
478                 .onCurrentProgramInfoChanged(stepUpInfo);
479     }
480 
481     @Test
step_withDirectionDown()482     public void step_withDirectionDown() throws Exception {
483         long initFreq = AM_FM_FREQUENCY_LIST[1];
484         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
485         RadioManager.ProgramInfo stepDownInfo = AidlTestUtils.makeProgramInfo(
486                 AidlTestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
487                 SIGNAL_QUALITY);
488         openAidlClients(/* numClients= */ 1);
489         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
490                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
491 
492         mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
493 
494         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
495                 .onCurrentProgramInfoChanged(stepDownInfo);
496     }
497 
498     @Test
step_forNonCurrentUser_doesNotStep()499     public void step_forNonCurrentUser_doesNotStep() throws Exception {
500         long initFreq = AM_FM_FREQUENCY_LIST[1];
501         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
502         openAidlClients(/* numClients= */ 1);
503         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
504                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
505         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
506 
507         mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
508 
509         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
510                 .onCurrentProgramInfoChanged(any());
511     }
512 
513     @Test
step_withHalInInvalidState_fails()514     public void step_withHalInInvalidState_fails() throws Exception {
515         openAidlClients(/* numClients= */ 1);
516         doThrow(new ServiceSpecificException(Result.INVALID_STATE))
517                 .when(mBroadcastRadioMock).step(anyBoolean());
518 
519         IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
520             mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
521         });
522 
523         assertWithMessage("Exception for stepping when HAL is in invalid state")
524                 .that(thrown).hasMessageThat().contains("INVALID_STATE");
525     }
526 
527     @Test
seek_withDirectionUp()528     public void seek_withDirectionUp() throws Exception {
529         long initFreq = AM_FM_FREQUENCY_LIST[2];
530         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
531         RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
532                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
533                 SIGNAL_QUALITY);
534         openAidlClients(/* numClients= */ 1);
535         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
536                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
537 
538         mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
539 
540         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
541                 .onCurrentProgramInfoChanged(seekUpInfo);
542     }
543 
544     @Test
seek_callsOnTuneFailedWhenTimeout()545     public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
546         int numSessions = 2;
547         openAidlClients(numSessions);
548 
549         mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
550 
551         for (int index = 0; index < numSessions; index++) {
552             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
553                     .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
554         }
555     }
556 
557     @Test
seek_withDirectionDown()558     public void seek_withDirectionDown() throws Exception {
559         long initFreq = AM_FM_FREQUENCY_LIST[2];
560         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
561         RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
562                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
563                 SIGNAL_QUALITY);
564         openAidlClients(/* numClients= */ 1);
565         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
566                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
567 
568         mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
569 
570         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
571                 .onCurrentProgramInfoChanged(seekUpInfo);
572     }
573 
574     @Test
seek_forNonCurrentUser_doesNotSeek()575     public void seek_forNonCurrentUser_doesNotSeek() throws Exception {
576         long initFreq = AM_FM_FREQUENCY_LIST[2];
577         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
578         RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
579                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
580                 SIGNAL_QUALITY);
581         openAidlClients(/* numClients= */ 1);
582         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
583                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
584         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
585 
586         mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
587 
588         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
589                 .onCurrentProgramInfoChanged(seekUpInfo);
590     }
591 
592     @Test
seek_withInternalErrorFromHal_fails()593     public void seek_withInternalErrorFromHal_fails() throws Exception {
594         openAidlClients(/* numClients= */ 1);
595         doThrow(new ServiceSpecificException(Result.INTERNAL_ERROR))
596                 .when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
597 
598         ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
599             mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
600         });
601 
602         assertWithMessage("Internal error HAL exception when seeking")
603                 .that(thrown).hasMessageThat().contains("INTERNAL_ERROR");
604     }
605 
606     @Test
cancel()607     public void cancel() throws Exception {
608         openAidlClients(/* numClients= */ 1);
609         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
610         mTunerSessions[0].tune(initialSel);
611 
612         mTunerSessions[0].cancel();
613 
614         verify(mBroadcastRadioMock).cancel();
615     }
616 
617     @Test
cancel_forNonCurrentUser_doesNotCancel()618     public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
619         openAidlClients(/* numClients= */ 1);
620         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
621         mTunerSessions[0].tune(initialSel);
622         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
623 
624         mTunerSessions[0].cancel();
625 
626         verify(mBroadcastRadioMock, never()).cancel();
627     }
628 
629     @Test
cancel_whenHalThrowsRemoteException_fails()630     public void cancel_whenHalThrowsRemoteException_fails() throws Exception {
631         openAidlClients(/* numClients= */ 1);
632         String exceptionMessage = "HAL service died.";
633         doThrow(new RemoteException(exceptionMessage)).when(mBroadcastRadioMock).cancel();
634 
635         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
636             mTunerSessions[0].cancel();
637         });
638 
639         assertWithMessage("Exception for canceling when HAL throws remote exception")
640                 .that(thrown).hasMessageThat().contains(exceptionMessage);
641     }
642 
643     @Test
getImage_withInvalidId_throwsIllegalArgumentException()644     public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
645         openAidlClients(/* numClients= */ 1);
646         int imageId = IBroadcastRadio.INVALID_IMAGE;
647 
648         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
649             mTunerSessions[0].getImage(imageId);
650         });
651 
652         assertWithMessage("Get image exception")
653                 .that(thrown).hasMessageThat().contains("Image ID is missing");
654     }
655 
656     @Test
getImage_withValidId()657     public void getImage_withValidId() throws Exception {
658         openAidlClients(/* numClients= */ 1);
659         int imageId = 1;
660 
661         Bitmap imageTest = mTunerSessions[0].getImage(imageId);
662 
663         assertWithMessage("Null image").that(imageTest).isEqualTo(null);
664     }
665 
666     @Test
getImage_whenHalThrowsException_fails()667     public void getImage_whenHalThrowsException_fails() throws Exception {
668         openAidlClients(/* numClients= */ 1);
669         String exceptionMessage = "HAL service died.";
670         when(mBroadcastRadioMock.getImage(anyInt()))
671                 .thenThrow(new RemoteException(exceptionMessage));
672 
673         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
674             mTunerSessions[0].getImage(/* id= */ 1);
675         });
676 
677         assertWithMessage("Exception for getting image when HAL throws remote exception")
678                 .that(thrown).hasMessageThat().contains(exceptionMessage);
679     }
680 
681     @Test
startBackgroundScan()682     public void startBackgroundScan() throws Exception {
683         openAidlClients(/* numClients= */ 1);
684 
685         mTunerSessions[0].startBackgroundScan();
686 
687         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
688     }
689 
690     @Test
startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback()691     public void startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
692         openAidlClients(/* numClients= */ 1);
693         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
694 
695         mTunerSessions[0].startBackgroundScan();
696 
697         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onBackgroundScanComplete();
698     }
699 
700     @Test
startProgramListUpdates_withEmptyFilter()701     public void startProgramListUpdates_withEmptyFilter() throws Exception {
702         openAidlClients(/* numClients= */ 1);
703         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
704                 /* includeCategories= */ true, /* excludeModifications= */ false);
705         ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(filter);
706         List<RadioManager.ProgramInfo> modified = List.of(TEST_FM_INFO, TEST_RDS_INFO);
707         List<ProgramSelector.Identifier> removed = new ArrayList<>();
708         ProgramListChunk halProgramList = AidlTestUtils.makeHalChunk(/* purge= */ true,
709                 /* complete= */ true, modified, removed);
710         ProgramList.Chunk expectedProgramList =
711                 AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true, modified, removed);
712 
713         mTunerSessions[0].startProgramListUpdates(filter);
714         mHalTunerCallback.onProgramListUpdated(halProgramList);
715 
716         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
717         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
718                 .onProgramListUpdated(expectedProgramList);
719     }
720 
721     @Test
startProgramListUpdates_withCallbackCalledForMultipleTimes()722     public void startProgramListUpdates_withCallbackCalledForMultipleTimes() throws Exception {
723         openAidlClients(/* numClients= */ 1);
724         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
725                 /* includeCategories= */ true, /* excludeModifications= */ false);
726         mTunerSessions[0].startProgramListUpdates(filter);
727         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
728                 /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
729         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
730                 AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
731                         List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
732 
733         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
734                 /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
735 
736         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
737                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
738                         List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
739     }
740 
741     @Test
startProgramListUpdates_withTheSameFilterForMultipleTimes()742     public void startProgramListUpdates_withTheSameFilterForMultipleTimes() throws Exception {
743         openAidlClients(/* numClients= */ 1);
744         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
745                 /* includeCategories= */ true, /* excludeModifications= */ false);
746         mTunerSessions[0].startProgramListUpdates(filter);
747         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
748                 /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
749         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
750                 AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
751                         List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
752         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
753                 /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
754         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
755                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
756                         List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
757 
758         mTunerSessions[0].startProgramListUpdates(filter);
759 
760         verify(mBroadcastRadioMock).startProgramListUpdates(any());
761         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
762                 AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
763                         List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
764     }
765 
766     @Test
startProgramListUpdates_withNullFilter()767     public void startProgramListUpdates_withNullFilter() throws Exception {
768         openAidlClients(/* numClients= */ 1);
769 
770         mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
771         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ true,
772                 /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
773 
774         verify(mBroadcastRadioMock).startProgramListUpdates(any());
775         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
776                 AidlTestUtils.makeChunk(/* purge= */ true, /* complete= */ true,
777                         List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
778 
779         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
780                 /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
781 
782         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
783                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
784                         List.of(TEST_FM_INFO_MODIFIED), List.of(TEST_RDS_PI_ID)));
785     }
786 
787     @Test
startProgramListUpdates_withIdFilter()788     public void startProgramListUpdates_withIdFilter() throws Exception {
789         openAidlClients(/* numClients= */ 1);
790         ProgramList.Filter idFilter = new ProgramList.Filter(new ArraySet<>(),
791                 Set.of(TEST_RDS_PI_ID), /* includeCategories= */ true,
792                 /* excludeModifications= */ true);
793         ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(idFilter);
794 
795         mTunerSessions[0].startProgramListUpdates(idFilter);
796         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
797                 /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>()));
798 
799         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
800         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
801                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
802                         List.of(TEST_RDS_INFO), new ArrayList<>()));
803 
804         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
805                 /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
806 
807         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any());
808     }
809 
810     @Test
startProgramListUpdates_withFilterExcludingModifications()811     public void startProgramListUpdates_withFilterExcludingModifications() throws Exception {
812         openAidlClients(/* numClients= */ 1);
813         ProgramList.Filter filterExcludingModifications = new ProgramList.Filter(
814                 Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
815                 /* includeCategories= */ true, /* excludeModifications= */ true);
816         ProgramFilter halFilter =
817                 ConversionUtils.filterToHalProgramFilter(filterExcludingModifications);
818 
819         mTunerSessions[0].startProgramListUpdates(filterExcludingModifications);
820         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
821                 /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
822 
823         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
824         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
825                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
826                         List.of(TEST_FM_INFO), new ArrayList<>()));
827 
828         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
829                 /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
830 
831         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(any());
832     }
833 
834     @Test
startProgramListUpdates_withFilterIncludingModifications()835     public void startProgramListUpdates_withFilterIncludingModifications() throws Exception {
836         openAidlClients(/* numClients= */ 1);
837         ProgramList.Filter filterIncludingModifications = new ProgramList.Filter(
838                 Set.of(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY), new ArraySet<>(),
839                 /* includeCategories= */ true, /* excludeModifications= */ false);
840         ProgramFilter halFilter =
841                 ConversionUtils.filterToHalProgramFilter(filterIncludingModifications);
842 
843         mTunerSessions[0].startProgramListUpdates(filterIncludingModifications);
844         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
845                 /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
846 
847         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
848         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
849                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
850                         List.of(TEST_FM_INFO), new ArrayList<>()));
851 
852         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
853                 /* complete= */ true, List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
854 
855         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onProgramListUpdated(
856                 AidlTestUtils.makeChunk(/* purge= */ false, /* complete= */ true,
857                         List.of(TEST_FM_INFO_MODIFIED), new ArrayList<>()));
858     }
859 
860     @Test
onProgramListUpdated_afterSessionClosed_doesNotUpdates()861     public void onProgramListUpdated_afterSessionClosed_doesNotUpdates() throws Exception {
862         openAidlClients(/* numClients= */ 1);
863         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
864                 /* includeCategories= */ true, /* excludeModifications= */ false);
865         mTunerSessions[0].startProgramListUpdates(filter);
866 
867         mTunerSessions[0].close();
868 
869         verify(mBroadcastRadioMock).stopProgramListUpdates();
870 
871         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
872                 /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
873 
874         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onProgramListUpdated(any());
875     }
876 
877     @Test
startProgramListUpdates_forMultipleSessions()878     public void startProgramListUpdates_forMultipleSessions() throws Exception {
879         int numSessions = 3;
880         openAidlClients(numSessions);
881         ProgramList.Filter fmIdFilter = new ProgramList.Filter(new ArraySet<>(),
882                 Set.of(TEST_FM_FREQUENCY_ID), /* includeCategories= */ false,
883                 /* excludeModifications= */ true);
884         ProgramList.Filter filterExcludingCategories = new ProgramList.Filter(new ArraySet<>(),
885                 new ArraySet<>(), /* includeCategories= */ true,
886                 /* excludeModifications= */ true);
887         ProgramList.Filter rdsTypeFilter = new ProgramList.Filter(
888                 Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(),
889                 /* includeCategories= */ true, /* excludeModifications= */ false);
890 
891         mTunerSessions[0].startProgramListUpdates(fmIdFilter);
892 
893         ProgramFilter halFilter = ConversionUtils.filterToHalProgramFilter(fmIdFilter);
894         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
895 
896         mTunerSessions[1].startProgramListUpdates(filterExcludingCategories);
897 
898         halFilter.identifiers = new android.hardware.broadcastradio.ProgramIdentifier[]{};
899         halFilter.includeCategories = true;
900         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
901 
902         mTunerSessions[2].startProgramListUpdates(rdsTypeFilter);
903 
904         halFilter.excludeModifications = false;
905         verify(mBroadcastRadioMock).startProgramListUpdates(halFilter);
906     }
907 
908     @Test
onProgramListUpdated_forMultipleSessions()909     public void onProgramListUpdated_forMultipleSessions() throws Exception {
910         int numSessions = 3;
911         openAidlClients(numSessions);
912         List<ProgramList.Filter> filters = List.of(new ProgramList.Filter(
913                         Set.of(ProgramSelector.IDENTIFIER_TYPE_RDS_PI), new ArraySet<>(),
914                         /* includeCategories= */ true, /* excludeModifications= */ false),
915                 new ProgramList.Filter(new ArraySet<>(), Set.of(TEST_FM_FREQUENCY_ID),
916                         /* includeCategories= */ false, /* excludeModifications= */ true),
917                 new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
918                         /* includeCategories= */ true, /* excludeModifications= */ true));
919 
920         for (int index = 0; index < numSessions; index++) {
921             mTunerSessions[index].startProgramListUpdates(filters.get(index));
922         }
923 
924         mHalTunerCallback.onProgramListUpdated(AidlTestUtils.makeHalChunk(/* purge= */ false,
925                 /* complete= */ true, List.of(TEST_FM_INFO, TEST_RDS_INFO), new ArrayList<>()));
926 
927         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
928                 .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
929                         /* complete= */ true, List.of(TEST_RDS_INFO), new ArrayList<>()));
930         verify(mAidlTunerCallbackMocks[1], CALLBACK_TIMEOUT)
931                 .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
932                         /* complete= */ true, List.of(TEST_FM_INFO), new ArrayList<>()));
933         verify(mAidlTunerCallbackMocks[2], CALLBACK_TIMEOUT)
934                 .onProgramListUpdated(AidlTestUtils.makeChunk(/* purge= */ false,
935                         /* complete= */ true, List.of(TEST_RDS_INFO, TEST_FM_INFO),
936                         new ArrayList<>()));
937     }
938 
939     @Test
startProgramListUpdates_forNonCurrentUser_doesNotStartUpdates()940     public void startProgramListUpdates_forNonCurrentUser_doesNotStartUpdates() throws Exception {
941         openAidlClients(/* numClients= */ 1);
942         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
943                 /* includeCategories= */ true, /* excludeModifications= */ false);
944         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
945 
946         mTunerSessions[0].startProgramListUpdates(filter);
947 
948         verify(mBroadcastRadioMock, never()).startProgramListUpdates(any());
949     }
950 
951     @Test
startProgramListUpdates_withUnknownErrorFromHal_fails()952     public void startProgramListUpdates_withUnknownErrorFromHal_fails() throws Exception {
953         openAidlClients(/* numClients= */ 1);
954         doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
955                 .when(mBroadcastRadioMock).startProgramListUpdates(any());
956 
957         ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
958             mTunerSessions[0].startProgramListUpdates(/* filter= */ null);
959         });
960 
961         assertWithMessage("Unknown error HAL exception when updating program list")
962                 .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
963     }
964 
965     @Test
stopProgramListUpdates()966     public void stopProgramListUpdates() throws Exception {
967         openAidlClients(/* numClients= */ 1);
968         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
969                 /* includeCategories= */ true, /* excludeModifications= */ false);
970         mTunerSessions[0].startProgramListUpdates(filter);
971 
972         mTunerSessions[0].stopProgramListUpdates();
973 
974         verify(mBroadcastRadioMock).stopProgramListUpdates();
975     }
976 
977     @Test
stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates()978     public void stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates() throws Exception {
979         openAidlClients(/* numClients= */ 1);
980         ProgramList.Filter filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
981                 /* includeCategories= */ true, /* excludeModifications= */ false);
982         mTunerSessions[0].startProgramListUpdates(filter);
983         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
984 
985         mTunerSessions[0].stopProgramListUpdates();
986 
987         verify(mBroadcastRadioMock, never()).stopProgramListUpdates();
988     }
989 
990     @Test
isConfigFlagSupported_withUnsupportedFlag_returnsFalse()991     public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
992         openAidlClients(/* numClients= */ 1);
993         int flag = UNSUPPORTED_CONFIG_FLAG;
994 
995         boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
996 
997         verify(mBroadcastRadioMock).isConfigFlagSet(flag);
998         assertWithMessage("Config flag %s is supported", flag).that(isSupported).isFalse();
999     }
1000 
1001     @Test
isConfigFlagSupported_withSupportedFlag_returnsTrue()1002     public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws Exception {
1003         openAidlClients(/* numClients= */ 1);
1004         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1005 
1006         boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
1007 
1008         verify(mBroadcastRadioMock).isConfigFlagSet(flag);
1009         assertWithMessage("Config flag %s is supported", flag).that(isSupported).isTrue();
1010     }
1011 
1012     @Test
setConfigFlag_withUnsupportedFlag_throwsRuntimeException()1013     public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws Exception {
1014         openAidlClients(/* numClients= */ 1);
1015         int flag = UNSUPPORTED_CONFIG_FLAG;
1016 
1017         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
1018             mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
1019         });
1020 
1021         assertWithMessage("Exception for setting unsupported flag %s", flag)
1022                 .that(thrown).hasMessageThat().contains("setConfigFlag: NOT_SUPPORTED");
1023     }
1024 
1025     @Test
setConfigFlag_withFlagSetToTrue()1026     public void setConfigFlag_withFlagSetToTrue() throws Exception {
1027         openAidlClients(/* numClients= */ 1);
1028         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1029 
1030         mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
1031 
1032         verify(mBroadcastRadioMock).setConfigFlag(flag, /* value= */ true);
1033     }
1034 
1035     @Test
setConfigFlag_withFlagSetToFalse()1036     public void setConfigFlag_withFlagSetToFalse() throws Exception {
1037         openAidlClients(/* numClients= */ 1);
1038         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1039 
1040         mTunerSessions[0].setConfigFlag(flag, /* value= */ false);
1041 
1042         verify(mBroadcastRadioMock).setConfigFlag(flag, /* value= */ false);
1043     }
1044 
1045     @Test
setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag()1046     public void setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag() throws Exception {
1047         openAidlClients(/* numClients= */ 1);
1048         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1049         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
1050 
1051         mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
1052 
1053         verify(mBroadcastRadioMock, never()).setConfigFlag(flag, /* value= */ true);
1054     }
1055 
1056     @Test
isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()1057     public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
1058             throws Exception {
1059         openAidlClients(/* numClients= */ 1);
1060         int flag = UNSUPPORTED_CONFIG_FLAG;
1061 
1062         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
1063             mTunerSessions[0].isConfigFlagSet(flag);
1064         });
1065 
1066         assertWithMessage("Exception for checking if unsupported flag %s is set", flag)
1067                 .that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
1068     }
1069 
1070     @Test
isConfigFlagSet_withSupportedFlag()1071     public void isConfigFlagSet_withSupportedFlag() throws Exception {
1072         openAidlClients(/* numClients= */ 1);
1073         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1074         boolean expectedConfigFlagValue = true;
1075         mTunerSessions[0].setConfigFlag(flag, /* value= */ expectedConfigFlagValue);
1076 
1077         boolean isSet = mTunerSessions[0].isConfigFlagSet(flag);
1078 
1079         assertWithMessage("Config flag %s is set", flag)
1080                 .that(isSet).isEqualTo(expectedConfigFlagValue);
1081     }
1082 
1083     @Test
isConfigFlagSet_whenHalThrowsRemoteException_fails()1084     public void isConfigFlagSet_whenHalThrowsRemoteException_fails() throws Exception {
1085         openAidlClients(/* numClients= */ 1);
1086         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1087         doThrow(new RemoteException()).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
1088 
1089         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
1090             mTunerSessions[0].isConfigFlagSet(flag);
1091         });
1092 
1093         assertWithMessage("Exception for checking config flag when HAL throws remote exception")
1094                 .that(thrown).hasMessageThat().contains("Failed to check flag");
1095     }
1096 
1097     @Test
setParameters_withMockParameters()1098     public void setParameters_withMockParameters() throws Exception {
1099         openAidlClients(/* numClients= */ 1);
1100         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
1101                 "mockParam2", "mockValue2");
1102 
1103         mTunerSessions[0].setParameters(parametersSet);
1104 
1105         verify(mBroadcastRadioMock).setParameters(
1106                 ConversionUtils.vendorInfoToHalVendorKeyValues(parametersSet));
1107     }
1108 
1109     @Test
setParameters_forNonCurrentUser_doesNotSetParameters()1110     public void setParameters_forNonCurrentUser_doesNotSetParameters() throws Exception {
1111         openAidlClients(/* numClients= */ 1);
1112         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
1113                 "mockParam2", "mockValue2");
1114         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
1115 
1116         mTunerSessions[0].setParameters(parametersSet);
1117 
1118         verify(mBroadcastRadioMock, never()).setParameters(any());
1119     }
1120 
1121     @Test
setParameters_whenHalThrowsRemoteException_fails()1122     public void setParameters_whenHalThrowsRemoteException_fails() throws Exception {
1123         openAidlClients(/* numClients= */ 1);
1124         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
1125                 "mockParam2", "mockValue2");
1126         String exceptionMessage = "HAL service died.";
1127         when(mBroadcastRadioMock.setParameters(any()))
1128                 .thenThrow(new RemoteException(exceptionMessage));
1129 
1130         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
1131             mTunerSessions[0].setParameters(parametersSet);
1132         });
1133 
1134         assertWithMessage("Exception for setting parameters when HAL throws remote exception")
1135                 .that(thrown).hasMessageThat().contains(exceptionMessage);
1136     }
1137 
1138     @Test
getParameters_withMockKeys()1139     public void getParameters_withMockKeys() throws Exception {
1140         openAidlClients(/* numClients= */ 1);
1141         List<String> parameterKeys = List.of("mockKey1", "mockKey2");
1142 
1143         mTunerSessions[0].getParameters(parameterKeys);
1144 
1145         verify(mBroadcastRadioMock).getParameters(parameterKeys.toArray(new String[0]));
1146     }
1147 
1148     @Test
getParameters_whenServiceThrowsRemoteException_fails()1149     public void getParameters_whenServiceThrowsRemoteException_fails() throws Exception {
1150         openAidlClients(/* numClients= */ 1);
1151         List<String> parameterKeys = List.of("mockKey1", "mockKey2");
1152         String exceptionMessage = "HAL service died.";
1153         when(mBroadcastRadioMock.getParameters(any()))
1154                 .thenThrow(new RemoteException(exceptionMessage));
1155 
1156         RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
1157             mTunerSessions[0].getParameters(parameterKeys);
1158         });
1159 
1160         assertWithMessage("Exception for getting parameters when HAL throws remote exception")
1161                 .that(thrown).hasMessageThat().contains(exceptionMessage);
1162     }
1163 
1164     @Test
onCurrentProgramInfoChanged_withNoncurrentUser_doesNotInvokeCallback()1165     public void onCurrentProgramInfoChanged_withNoncurrentUser_doesNotInvokeCallback()
1166             throws Exception {
1167         openAidlClients(1);
1168         doReturn(USER_ID_2).when(() -> RadioServiceUserController.getCurrentUser());
1169 
1170         mHalTunerCallback.onCurrentProgramInfoChanged(AidlTestUtils.makeHalProgramInfo(
1171                 AidlTestUtils.makeHalFmSelector(AM_FM_FREQUENCY_LIST[1]), SIGNAL_QUALITY));
1172 
1173         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
1174                 .onCurrentProgramInfoChanged(any());
1175     }
1176 
1177     @Test
onTuneFailed_forTunerCallback()1178     public void onTuneFailed_forTunerCallback() throws Exception {
1179         int numSessions = 3;
1180         openAidlClients(numSessions);
1181         android.hardware.broadcastradio.ProgramSelector halSel = AidlTestUtils.makeHalFmSelector(
1182                 AM_FM_FREQUENCY_LIST[1]);
1183         ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
1184 
1185         mHalTunerCallback.onTuneFailed(Result.CANCELED, halSel);
1186 
1187         for (int index = 0; index < numSessions; index++) {
1188             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
1189                     .onTuneFailed(RadioTuner.TUNER_RESULT_CANCELED, sel);
1190         }
1191     }
1192 
1193     @Test
onAntennaStateChange_forTunerCallback()1194     public void onAntennaStateChange_forTunerCallback() throws Exception {
1195         int numSessions = 3;
1196         openAidlClients(numSessions);
1197 
1198         mHalTunerCallback.onAntennaStateChange(/* connected= */ false);
1199 
1200         for (int index = 0; index < numSessions; index++) {
1201             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
1202                     .onAntennaState(/* connected= */ false);
1203         }
1204     }
1205 
1206     @Test
onConfigFlagUpdated_forTunerCallback()1207     public void onConfigFlagUpdated_forTunerCallback() throws Exception {
1208         int numSessions = 3;
1209         openAidlClients(numSessions);
1210         int flag = UNSUPPORTED_CONFIG_FLAG + 1;
1211         boolean configFlagValue = true;
1212 
1213         mHalTunerCallback.onConfigFlagUpdated(flag, configFlagValue);
1214 
1215         for (int index = 0; index < numSessions; index++) {
1216             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
1217                     .onConfigFlagUpdated(flag, configFlagValue);
1218         }
1219     }
1220 
1221     @Test
onParametersUpdated_forTunerCallback()1222     public void onParametersUpdated_forTunerCallback() throws Exception {
1223         int numSessions = 3;
1224         openAidlClients(numSessions);
1225         VendorKeyValue[] parametersUpdates = {
1226                 AidlTestUtils.makeVendorKeyValue("com.vendor.parameter1", "value1")};
1227         Map<String, String> parametersExpected = Map.of("com.vendor.parameter1", "value1");
1228 
1229         mHalTunerCallback.onParametersUpdated(parametersUpdates);
1230 
1231         for (int index = 0; index < numSessions; index++) {
1232             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
1233                     .onParametersUpdated(parametersExpected);
1234         }
1235     }
1236 
openAidlClients(int numClients)1237     private void openAidlClients(int numClients) throws Exception {
1238         mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
1239         mTunerSessions = new TunerSession[numClients];
1240         for (int index = 0; index < numClients; index++) {
1241             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
1242             mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
1243         }
1244     }
1245 
getSeekFrequency(long currentFrequency, boolean seekDown)1246     private long getSeekFrequency(long currentFrequency, boolean seekDown) {
1247         long seekFrequency;
1248         if (seekDown) {
1249             seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
1250             for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
1251                 if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
1252                     seekFrequency = AM_FM_FREQUENCY_LIST[i];
1253                     break;
1254                 }
1255             }
1256         } else {
1257             seekFrequency = AM_FM_FREQUENCY_LIST[0];
1258             for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
1259                 if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
1260                     seekFrequency = AM_FM_FREQUENCY_LIST[index];
1261                     break;
1262                 }
1263             }
1264         }
1265         return seekFrequency;
1266     }
1267 }
1268