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