1 /*
2  * Copyright (C) 2021 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.hdmi;
18 
19 
20 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
21 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
22 import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.mockito.Mockito.spy;
27 
28 import android.content.Context;
29 import android.content.ContextWrapper;
30 import android.hardware.hdmi.HdmiDeviceInfo;
31 import android.hardware.hdmi.HdmiPortInfo;
32 import android.os.Looper;
33 import android.os.test.TestLooper;
34 import android.platform.test.annotations.Presubmit;
35 
36 import androidx.test.InstrumentationRegistry;
37 import androidx.test.filters.SmallTest;
38 
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.JUnit4;
43 
44 import java.util.Collections;
45 
46 /**
47  * Test for {@link SystemAudioAutoInitiationAction}.
48  */
49 @SmallTest
50 @Presubmit
51 @RunWith(JUnit4.class)
52 public class SystemAudioAutoInitiationActionTest {
53 
54     private Context mContextSpy;
55     private HdmiControlService mHdmiControlService;
56     private FakeNativeWrapper mNativeWrapper;
57     private FakePowerManagerWrapper mPowerManager;
58 
59     private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
60 
61     private TestLooper mTestLooper = new TestLooper();
62     private int mPhysicalAddress;
63 
64     @Before
setUp()65     public void setUp() throws Exception {
66         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
67 
68         Looper myLooper = mTestLooper.getLooper();
69 
70         FakeAudioFramework audioFramework = new FakeAudioFramework();
71 
72         mHdmiControlService = new HdmiControlService(mContextSpy,
73                 Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
74                 audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager()) {
75             @Override
76             boolean isPowerStandby() {
77                 return false;
78             }
79 
80             @Override
81             protected void writeStringSystemProperty(String key, String value) {
82                 // do nothing
83             }
84         };
85 
86         mHdmiControlService.setIoLooper(myLooper);
87         mNativeWrapper = new FakeNativeWrapper();
88         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
89                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
90         mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
91         mHdmiControlService.setCecController(hdmiCecController);
92         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
93         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
94         hdmiPortInfos[0] =
95                 new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x1000)
96                         .setCecSupported(true)
97                         .setMhlSupported(false)
98                         .setArcSupported(false)
99                         .build();
100         hdmiPortInfos[1] =
101                 new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, 0x2000)
102                         .setCecSupported(true)
103                         .setMhlSupported(false)
104                         .setArcSupported(true)
105                         .build();
106         mNativeWrapper.setPortInfo(hdmiPortInfos);
107         mHdmiControlService.initService();
108         mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
109         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
110         mHdmiControlService.setPowerManager(mPowerManager);
111         mPhysicalAddress = 0x0000;
112         mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
113         mTestLooper.dispatchAll();
114         mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
115         mPhysicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
116         mNativeWrapper.clearResultMessages();
117     }
118 
setSystemAudioSetting(boolean on)119     private void setSystemAudioSetting(boolean on) {
120         mHdmiCecLocalDeviceTv.setSystemAudioControlFeatureEnabled(on);
121     }
122 
setTvHasSystemAudioChangeAction()123     private void setTvHasSystemAudioChangeAction() {
124         mHdmiCecLocalDeviceTv.addAndStartAction(
125                 new SystemAudioActionFromTv(mHdmiCecLocalDeviceTv, Constants.ADDR_AUDIO_SYSTEM,
126                         true, null));
127     }
128 
129     @Test
testReceiveSystemAudioMode_systemAudioOn()130     public void testReceiveSystemAudioMode_systemAudioOn() {
131         // Record that previous system audio mode is on.
132         setSystemAudioSetting(true);
133 
134         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
135                 ADDR_AUDIO_SYSTEM);
136         mHdmiCecLocalDeviceTv.addAndStartAction(action);
137         mTestLooper.dispatchAll();
138 
139         HdmiCecMessage giveSystemAudioModeStatus =
140                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
141                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
142                         ADDR_AUDIO_SYSTEM);
143 
144         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
145 
146         HdmiCecMessage reportSystemAudioMode =
147                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
148                         ADDR_AUDIO_SYSTEM,
149                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
150                         true);
151         mHdmiControlService.handleCecCommand(reportSystemAudioMode);
152         mTestLooper.dispatchAll();
153 
154         assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue();
155     }
156 
157     @Test
testReceiveSystemAudioMode_systemAudioOnAndImpossibleToChangeSystemAudio()158     public void testReceiveSystemAudioMode_systemAudioOnAndImpossibleToChangeSystemAudio() {
159         // Turn on system audio.
160         setSystemAudioSetting(true);
161         // Impossible to change system audio mode while SystemAudioActionFromTv is in progress.
162         setTvHasSystemAudioChangeAction();
163 
164         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
165                 ADDR_AUDIO_SYSTEM);
166         mHdmiCecLocalDeviceTv.addAndStartAction(action);
167         mTestLooper.dispatchAll();
168 
169         HdmiCecMessage giveSystemAudioModeStatus =
170                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
171                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
172                         ADDR_AUDIO_SYSTEM);
173 
174         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
175 
176         HdmiCecMessage reportSystemAudioMode =
177                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
178                         ADDR_AUDIO_SYSTEM,
179                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
180                         true);
181         mHdmiControlService.handleCecCommand(reportSystemAudioMode);
182         mTestLooper.dispatchAll();
183 
184         assertThat(mHdmiControlService.isSystemAudioActivated()).isFalse();
185     }
186 
187     @Test
testReceiveSystemAudioMode_systemAudioOnAndResponseOff()188     public void testReceiveSystemAudioMode_systemAudioOnAndResponseOff() {
189         // Record that previous system audio mode is on.
190         setSystemAudioSetting(true);
191 
192         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
193                 ADDR_AUDIO_SYSTEM);
194         mHdmiCecLocalDeviceTv.addAndStartAction(action);
195         mTestLooper.dispatchAll();
196 
197         HdmiCecMessage giveSystemAudioModeStatus =
198                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
199                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
200                         ADDR_AUDIO_SYSTEM);
201 
202         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
203 
204         HdmiCecMessage reportSystemAudioMode =
205                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
206                         ADDR_AUDIO_SYSTEM,
207                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
208                         false);
209         mHdmiControlService.handleCecCommand(reportSystemAudioMode);
210         mTestLooper.dispatchAll();
211 
212         assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty();
213         SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions(
214                 SystemAudioActionFromTv.class).get(0);
215         assertThat(resultingAction.mTargetAudioStatus).isTrue();
216     }
217 
218     @Test
testReceiveSystemAudioMode_settingOffAndResponseOn()219     public void testReceiveSystemAudioMode_settingOffAndResponseOn() {
220         // Turn off system audio.
221         setSystemAudioSetting(false);
222 
223         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
224                 ADDR_AUDIO_SYSTEM);
225         mHdmiCecLocalDeviceTv.addAndStartAction(action);
226         mTestLooper.dispatchAll();
227 
228         HdmiCecMessage giveSystemAudioModeStatus =
229                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
230                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
231                         ADDR_AUDIO_SYSTEM);
232 
233         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
234 
235         HdmiCecMessage reportSystemAudioMode =
236                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
237                         ADDR_AUDIO_SYSTEM,
238                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
239                         true);
240         mHdmiControlService.handleCecCommand(reportSystemAudioMode);
241         mTestLooper.dispatchAll();
242 
243         assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty();
244         SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions(
245                 SystemAudioActionFromTv.class).get(0);
246         assertThat(resultingAction.mTargetAudioStatus).isFalse();
247     }
248 
249     @Test
testReceiveSystemAudioMode_settingOffAndResponseOff()250     public void testReceiveSystemAudioMode_settingOffAndResponseOff() {
251         // Turn off system audio.
252         setSystemAudioSetting(false);
253 
254         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
255                 ADDR_AUDIO_SYSTEM);
256         mHdmiCecLocalDeviceTv.addAndStartAction(action);
257         mTestLooper.dispatchAll();
258 
259         HdmiCecMessage giveSystemAudioModeStatus =
260                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
261                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
262                         ADDR_AUDIO_SYSTEM);
263 
264         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
265 
266         HdmiCecMessage reportSystemAudioMode =
267                 HdmiCecMessageBuilder.buildReportSystemAudioMode(
268                         ADDR_AUDIO_SYSTEM,
269                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
270                         false);
271         mHdmiControlService.handleCecCommand(reportSystemAudioMode);
272         mTestLooper.dispatchAll();
273 
274         assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isEmpty();
275         assertThat(mHdmiControlService.isSystemAudioActivated()).isFalse();
276     }
277 
278     @Test
testTimeout_systemAudioOn_retries()279     public void testTimeout_systemAudioOn_retries() {
280         // Turn on system audio.
281         setSystemAudioSetting(true);
282 
283         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
284                 ADDR_AUDIO_SYSTEM);
285         mHdmiCecLocalDeviceTv.addAndStartAction(action);
286         mTestLooper.dispatchAll();
287 
288         HdmiCecMessage giveSystemAudioModeStatus =
289                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
290                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
291                         ADDR_AUDIO_SYSTEM);
292 
293         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
294         mNativeWrapper.clearResultMessages();
295 
296         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
297         mTestLooper.dispatchAll();
298 
299         // Retry sends <Give System Audio Mode Status> again
300         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
301     }
302 
303     @Test
testTimeout_systemAudioOn_allRetriesFail()304     public void testTimeout_systemAudioOn_allRetriesFail() {
305         boolean targetStatus = true;
306         // Turn on system audio.
307         setSystemAudioSetting(targetStatus);
308 
309         HdmiCecFeatureAction action = new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv,
310                 ADDR_AUDIO_SYSTEM);
311         mHdmiCecLocalDeviceTv.addAndStartAction(action);
312         mTestLooper.dispatchAll();
313 
314         HdmiCecMessage giveSystemAudioModeStatus =
315                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
316                         mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(),
317                         ADDR_AUDIO_SYSTEM);
318         assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
319 
320         for (int i = 0; i < RETRIES_ON_TIMEOUT; i++) {
321             mNativeWrapper.clearResultMessages();
322 
323             // Target device doesn't respond within timeout
324             mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
325             mTestLooper.dispatchAll();
326 
327             // Retry sends <Give System Audio Mode Status> again
328             assertThat(mNativeWrapper.getResultMessages()).contains(giveSystemAudioModeStatus);
329         }
330 
331         // Target device doesn't respond within timeouts
332         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
333         mTestLooper.dispatchAll();
334 
335         assertThat(mHdmiCecLocalDeviceTv.getActions(SystemAudioActionFromTv.class)).isNotEmpty();
336         SystemAudioActionFromTv resultingAction = mHdmiCecLocalDeviceTv.getActions(
337                 SystemAudioActionFromTv.class).get(0);
338         assertThat(resultingAction.mTargetAudioStatus).isEqualTo(targetStatus);
339     }
340 }
341