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 <Set Stream Path> 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 <Set Stream Path> / the device does
not wake up. Tested with a Sony TV.
Change-Id: I7cae85828b58d482d65f5e645f8940dd92b07f99
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user