Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 610e9d4a authored by Robert Horvath's avatar Robert Horvath Committed by Aaron Kling
Browse files

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
parent 03ea07d9
Loading
Loading
Loading
Loading
+45 −5
Original line number Diff line number Diff line
@@ -453,7 +453,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);
@@ -500,6 +500,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;
@@ -515,7 +537,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);
        }
    }

@@ -541,9 +565,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()) {
@@ -567,6 +589,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[] {
@@ -2418,6 +2451,13 @@ public class HdmiControlService extends SystemService {
        return mPowerStatus;
    }

    @ServiceThreadOnly
    @VisibleForTesting
    void setPowerStatus(int powerStatus) {
        assertRunOnServiceThread();
        mPowerStatus = powerStatus;
    }

    @ServiceThreadOnly
    boolean isPowerOnOrTransient() {
        assertRunOnServiceThread();
+59 −8
Original line number Diff line number Diff line
@@ -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,10 +122,19 @@ public class HdmiControlServiceTest {
    private boolean mStandbyMessageReceived;
    private HdmiPortInfo[] mHdmiPortInfo;

    @Mock private IPowerManager mIPowerManagerMock;

    @Before
    public void SetUp() {
        mHdmiControlService =
                new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
    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;
@@ -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);
    }
}