From 04848a3e17b52ebfd3ca99bed49a295fe801fa50 Mon Sep 17 00:00:00 2001 From: Robert Horvath Date: Fri, 29 Nov 2019 16:14:42 +0100 Subject: [PATCH] Initialize HDMI PowerStatus to standby In quiescent boot, the screen stays off until explicit user action wakes it up. The device should not become an active HDMI source in quiescent boot. This commit changes the initial power status of the CEC device. The CEC device will go into standby PowerStatus initially, and once boot is completed check if the device is interactive. If the device is interactive after boot complete, then the CEC device will transition into the "On" power state as well. Without this, the following problem occurs: - The connected TV is using the device as active source - The device performs a quiescent reboot - The device reports a power status of "on" - The TV sends a message to request a streaming path from the device - The device sends a wakeUp to PowerManager and becomes Active Source By reporting that the device is in standby power status, the TV should not try to request a streaming path from the device. Bug: 145265927 Test: atest HdmiControlServiceTest Test: `adb reboot quiescent`, check device reports standby power state in `adb shell dumpsys hdmi_control`. Check that TV does not request / the device does not wake up. Tested with a Sony TV. Change-Id: I7cae85828b58d482d65f5e645f8940dd92b07f99 --- .../server/hdmi/HdmiControlService.java | 50 ++++++++++++-- .../server/hdmi/HdmiControlServiceTest.java | 67 ++++++++++++++++--- 2 files changed, 104 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 1794df3b602e0..7c2ec78c1cbc4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -491,7 +491,7 @@ public class HdmiControlService extends SystemService { mIoThread.start(); mIoLooper = mIoThread.getLooper(); } - mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; + mPowerStatus = getInitialPowerStatus(); mProhibitMode = false; mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true); mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); @@ -538,6 +538,28 @@ public class HdmiControlService extends SystemService { mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); } + private void bootCompleted() { + // on boot, if device is interactive, set HDMI CEC state as powered on as well + if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) { + onWakeUp(); + } + } + + /** + * Returns the initial power status used when the HdmiControlService starts. + */ + @VisibleForTesting + int getInitialPowerStatus() { + // The initial power status is POWER_STATUS_TRANSIENT_TO_STANDBY. + // Once boot completes the service transitions to POWER_STATUS_ON if the device is + // interactive. + // Quiescent boot is a special boot mode, in which the screen stays off during boot + // and the device goes to sleep after boot has finished. + // We don't transition to POWER_STATUS_ON initially, as we might be booting in quiescent + // mode, during which we don't want to appear powered on to avoid being made active source. + return HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; + } + @VisibleForTesting void setCecController(HdmiCecController cecController) { mCecController = cecController; @@ -553,7 +575,9 @@ public class HdmiControlService extends SystemService { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mTvInputManager = (TvInputManager) getContext().getSystemService( Context.TV_INPUT_SERVICE); - mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); + mPowerManager = getContext().getSystemService(PowerManager.class); + } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { + runOnServiceThread(this::bootCompleted); } } @@ -579,9 +603,7 @@ public class HdmiControlService extends SystemService { * Called when the initialization of local devices is complete. */ private void onInitializeCecComplete(int initiatedBy) { - if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { - mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - } + updatePowerStatusOnInitializeCecComplete(); mWakeUpMessageReceived = false; if (isTvDeviceEnabled()) { @@ -606,6 +628,17 @@ public class HdmiControlService extends SystemService { } } + /** + * Updates the power status once the initialization of local devices is complete. + */ + private void updatePowerStatusOnInitializeCecComplete() { + if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { + mPowerStatus = HdmiControlManager.POWER_STATUS_ON; + } else if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { + mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; + } + } + private void registerContentObserver() { ContentResolver resolver = getContext().getContentResolver(); String[] settings = new String[] { @@ -2666,6 +2699,13 @@ public class HdmiControlService extends SystemService { return mPowerStatus; } + @ServiceThreadOnly + @VisibleForTesting + void setPowerStatus(int powerStatus) { + assertRunOnServiceThread(); + mPowerStatus = powerStatus; + } + @ServiceThreadOnly boolean isPowerOnOrTransient() { assertRunOnServiceThread(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 1f660742122d5..9e98427db7093 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; +import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -25,8 +26,17 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; +import android.os.IPowerManager; import android.os.Looper; +import android.os.PowerManager; +import android.os.RemoteException; import android.os.test.TestLooper; import androidx.test.InstrumentationRegistry; @@ -36,6 +46,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -98,6 +110,7 @@ public class HdmiControlServiceTest { } private static final String TAG = "HdmiControlServiceTest"; + private Context mContextSpy; private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceMyDevice mMyAudioSystemDevice; @@ -109,15 +122,24 @@ public class HdmiControlServiceTest { private boolean mStandbyMessageReceived; private HdmiPortInfo[] mHdmiPortInfo; + @Mock private IPowerManager mIPowerManagerMock; + @Before - public void SetUp() { - mHdmiControlService = - new HdmiControlService(InstrumentationRegistry.getTargetContext()) { - @Override - boolean isStandbyMessageReceived() { - return mStandbyMessageReceived; - } - }; + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + + PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null); + when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); + when(mIPowerManagerMock.isInteractive()).thenReturn(true); + + mHdmiControlService = new HdmiControlService(mContextSpy) { + @Override + boolean isStandbyMessageReceived() { + return mStandbyMessageReceived; + } + }; mMyLooper = mTestLooper.getLooper(); mMyAudioSystemDevice = @@ -198,4 +220,33 @@ public class HdmiControlServiceTest { mHdmiControlService.initPortInfo(); assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID); } + + @Test + public void initialPowerStatus_normalBoot_isTransientToStandby() { + assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); + } + + @Test + public void initialPowerStatus_quiescentBoot_isTransientToStandby() throws RemoteException { + when(mIPowerManagerMock.isInteractive()).thenReturn(false); + assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); + } + + @Test + public void powerStatusAfterBootComplete_normalBoot_isOn() { + mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); + mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED); + assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); + } + + @Test + public void powerStatusAfterBootComplete_quiescentBoot_isStandby() throws RemoteException { + when(mIPowerManagerMock.isInteractive()).thenReturn(false); + mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED); + assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_STANDBY); + } }