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

Commit 60bb98f4 authored by Marvin Ramin's avatar Marvin Ramin
Browse files

Broadcast CEC power status changes

If CEC 2.0 is used, broadcast power status changes to the network.
Transient power status changes are only broadcast when they are expected
to last >2s.

Bug: 166227834
Test: atest com.android.server.hdmi
Change-Id: I888c8d2aa6bcaca65c0db0de0195f1c3939465e9
parent 660f7cd8
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -201,7 +201,7 @@ public class HdmiCecMessageValidator {
        addValidationInfo(
                Constants.MESSAGE_REPORT_POWER_STATUS,
                new OneByteRangeValidator(0x00, 0x03),
                DEST_DIRECT);
                DEST_DIRECT | DEST_BROADCAST);

        // Messages for the General Protocol.
        addValidationInfo(Constants.MESSAGE_FEATURE_ABORT,
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.hdmi;

import static com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;

import android.hardware.hdmi.HdmiControlManager;

/**
 * Storage of HDMI-CEC power status and controls possible cases where power status changes must also
 * broadcast {@code <Report Power Status>} messages.
 *
 * All HDMI-CEC related power status changes should be done through this class.
 */
class HdmiCecPowerStatusController {

    private final HdmiControlService mHdmiControlService;

    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;

    HdmiCecPowerStatusController(HdmiControlService hdmiControlService) {
        mHdmiControlService = hdmiControlService;
    }

    int getPowerStatus() {
        return mPowerStatus;
    }

    boolean isPowerStatusOn() {
        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON;
    }

    boolean isPowerStatusStandby() {
        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
    }

    boolean isPowerStatusTransientToOn() {
        return mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
    }

    boolean isPowerStatusTransientToStandby() {
        return mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
    }

    @ServiceThreadOnly
    void setPowerStatus(int powerStatus) {
        setPowerStatus(powerStatus, true);
    }

    @ServiceThreadOnly
    void setPowerStatus(int powerStatus, boolean sendPowerStatusUpdate) {
        if (powerStatus == mPowerStatus) {
            return;
        }

        mPowerStatus = powerStatus;
        if (sendPowerStatusUpdate
                && mHdmiControlService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
            sendReportPowerStatus(mPowerStatus);
        }
    }

    private void sendReportPowerStatus(int powerStatus) {
        for (HdmiCecLocalDevice localDevice : mHdmiControlService.getAllLocalDevices()) {
            mHdmiControlService.sendCecCommand(
                    HdmiCecMessageBuilder.buildReportPowerStatus(localDevice.mAddress,
                            Constants.ADDR_BROADCAST, powerStatus));
        }
    }
}
+25 −20
Original line number Diff line number Diff line
@@ -350,8 +350,8 @@ public class HdmiControlService extends SystemService {

    private HdmiCecMessageValidator mMessageValidator;

    @ServiceThreadOnly
    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
    private final HdmiCecPowerStatusController mPowerStatusController =
            new HdmiCecPowerStatusController(this);

    @ServiceThreadOnly
    private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -530,7 +530,8 @@ public class HdmiControlService extends SystemService {
            mIoThread.start();
            mIoLooper = mIoThread.getLooper();
        }
        mPowerStatus = getInitialPowerStatus();

        mPowerStatusController.setPowerStatus(getInitialPowerStatus());
        mProhibitMode = false;
        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
        mHdmiCecVolumeControlEnabled = readBooleanSetting(
@@ -673,10 +674,12 @@ 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;
        if (mPowerStatusController.isPowerStatusTransientToOn()) {
            mHandler.post(() -> mPowerStatusController.setPowerStatus(
                    HdmiControlManager.POWER_STATUS_ON));
        } else if (mPowerStatusController.isPowerStatusTransientToStandby()) {
            mHandler.post(() -> mPowerStatusController.setPowerStatus(
                    HdmiControlManager.POWER_STATUS_STANDBY));
        }
    }

@@ -2191,7 +2194,7 @@ public class HdmiControlService extends SystemService {
            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");

            pw.println("mProhibitMode: " + mProhibitMode);
            pw.println("mPowerStatus: " + mPowerStatus);
            pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());

            // System settings
            pw.println("System_settings:");
@@ -2831,34 +2834,34 @@ public class HdmiControlService extends SystemService {
    @ServiceThreadOnly
    int getPowerStatus() {
        assertRunOnServiceThread();
        return mPowerStatus;
        return mPowerStatusController.getPowerStatus();
    }

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

    @ServiceThreadOnly
    boolean isPowerOnOrTransient() {
        assertRunOnServiceThread();
        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
        return mPowerStatusController.isPowerStatusOn()
                || mPowerStatusController.isPowerStatusTransientToOn();
    }

    @ServiceThreadOnly
    boolean isPowerStandbyOrTransient() {
        assertRunOnServiceThread();
        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
        return mPowerStatusController.isPowerStatusStandby()
                || mPowerStatusController.isPowerStatusTransientToStandby();
    }

    @ServiceThreadOnly
    boolean isPowerStandby() {
        assertRunOnServiceThread();
        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
        return mPowerStatusController.isPowerStatusStandby();
    }

    @ServiceThreadOnly
@@ -2895,7 +2898,8 @@ public class HdmiControlService extends SystemService {
    @ServiceThreadOnly
    private void onWakeUp(@WakeReason final int wakeUpAction) {
        assertRunOnServiceThread();
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
                false);
        if (mCecController != null) {
            if (mHdmiControlEnabled) {
                int startReason = -1;
@@ -2926,14 +2930,15 @@ public class HdmiControlService extends SystemService {
    @VisibleForTesting
    protected void onStandby(final int standbyAction) {
        assertRunOnServiceThread();
        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
                false);
        invokeVendorCommandListenersOnControlStateChanged(false,
                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);

        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();

        if (!isStandbyMessageReceived() && !canGoToStandby()) {
            mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
            mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
            for (HdmiCecLocalDevice device : devices) {
                device.onStandby(mStandbyMessageReceived, standbyAction);
            }
@@ -3013,10 +3018,10 @@ public class HdmiControlService extends SystemService {
        assertRunOnServiceThread();
        Slog.v(TAG, "onStandbyCompleted");

        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
        if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
            return;
        }
        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
        mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
        for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
            device.onStandby(mStandbyMessageReceived, standbyAction);
        }
+1 −1
Original line number Diff line number Diff line
@@ -67,8 +67,8 @@ public class HdmiCecMessageValidatorTest {
    public void isValid_reportPowerStatus() {
        assertMessageValidity("04:90:00").isEqualTo(OK);
        assertMessageValidity("04:90:03:05").isEqualTo(OK);
        assertMessageValidity("0F:90:00").isEqualTo(OK);

        assertMessageValidity("0F:90:00").isEqualTo(ERROR_DESTINATION);
        assertMessageValidity("F0:90").isEqualTo(ERROR_SOURCE);
        assertMessageValidity("04:90").isEqualTo(ERROR_PARAMETER_SHORT);
        assertMessageValidity("04:90:04").isEqualTo(ERROR_PARAMETER);
+264 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.hdmi;

import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;

import static com.google.common.truth.Truth.assertThat;

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.Handler;
import android.os.IPowerManager;
import android.os.IThermalService;
import android.os.Looper;
import android.os.PowerManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import com.android.server.SystemService;

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;

@SmallTest
@Presubmit
@RunWith(JUnit4.class)
/** Tests for {@link HdmiCecPowerStatusController} class. */
public class HdmiCecPowerStatusControllerTest {

    public static final int[] ARRAY_POWER_STATUS = new int[]{HdmiControlManager.POWER_STATUS_ON,
            HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
            HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY,
            HdmiControlManager.POWER_STATUS_STANDBY};
    private HdmiCecPowerStatusController mHdmiCecPowerStatusController;
    private FakeNativeWrapper mNativeWrapper;
    private TestLooper mTestLooper = new TestLooper();
    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
    private int mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_b;
    @Mock
    private IPowerManager mIPowerManagerMock;
    @Mock
    private IThermalService mIThermalServiceMock;
    private HdmiControlService mHdmiControlService;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
        Looper myLooper = mTestLooper.getLooper();
        PowerManager powerManager = new PowerManager(contextSpy, mIPowerManagerMock,
                mIThermalServiceMock, new Handler(myLooper));
        when(contextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
        when(contextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
        when(mIPowerManagerMock.isInteractive()).thenReturn(true);

        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(contextSpy);

        mHdmiControlService = new HdmiControlService(contextSpy) {
            @Override
            boolean isControlEnabled() {
                return true;
            }

            @Override
            boolean isPlaybackDevice() {
                return true;
            }

            @Override
            void writeStringSystemProperty(String key, String value) {
                // do nothing
            }

            @Override
            int getCecVersion() {
                return mHdmiCecVersion;
            }

            @Override
            boolean isPowerStandby() {
                return false;
            }

            @Override
            HdmiCecConfig getHdmiCecConfig() {
                return hdmiCecConfig;
            }
        };
        mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);

        HdmiCecLocalDevicePlayback hdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(
                mHdmiControlService);
        hdmiCecLocalDevicePlayback.init();
        mHdmiControlService.setIoLooper(myLooper);
        mNativeWrapper = new FakeNativeWrapper();
        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
        mHdmiControlService.setCecController(hdmiCecController);
        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
        mLocalDevices.add(hdmiCecLocalDevicePlayback);
        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
        hdmiPortInfos[0] =
                new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
        mNativeWrapper.setPortInfo(hdmiPortInfos);
        mNativeWrapper.setPortConnectionStatus(1, true);
        mHdmiControlService.initService();
        mHdmiControlService.getHdmiCecNetwork().initPortInfo();
        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
        mNativeWrapper.setPhysicalAddress(0x2000);
        mTestLooper.dispatchAll();

        mHdmiCecPowerStatusController = new HdmiCecPowerStatusController(mHdmiControlService);
        mNativeWrapper.clearResultMessages();
    }

    @Test
    public void setPowerStatus() {
        for (int status : ARRAY_POWER_STATUS) {
            mHdmiCecPowerStatusController.setPowerStatus(status);
            assertThat(mHdmiCecPowerStatusController.getPowerStatus()).isEqualTo(status);
        }
    }

    @Test
    public void isPowerStatusOn() {
        for (int status : ARRAY_POWER_STATUS) {
            mHdmiCecPowerStatusController.setPowerStatus(status);
            assertThat(mHdmiCecPowerStatusController.isPowerStatusOn()).isEqualTo(
                    HdmiControlManager.POWER_STATUS_ON == status);
        }
    }

    @Test
    public void isPowerStatusStandby() {
        for (int status : ARRAY_POWER_STATUS) {
            mHdmiCecPowerStatusController.setPowerStatus(status);
            assertThat(mHdmiCecPowerStatusController.isPowerStatusStandby()).isEqualTo(
                    HdmiControlManager.POWER_STATUS_STANDBY == status);
        }
    }

    @Test
    public void isPowerStatusTransientToOn() {
        for (int status : ARRAY_POWER_STATUS) {
            mHdmiCecPowerStatusController.setPowerStatus(status);
            assertThat(mHdmiCecPowerStatusController.isPowerStatusTransientToOn()).isEqualTo(
                    HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON == status);
        }
    }

    @Test
    public void isPowerStatusTransientToStandby() {
        for (int status : ARRAY_POWER_STATUS) {
            mHdmiCecPowerStatusController.setPowerStatus(status);
            assertThat(mHdmiCecPowerStatusController.isPowerStatusTransientToStandby()).isEqualTo(
                    HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY == status);
        }
    }

    @Test
    public void setPowerStatus_doesntSendBroadcast_1_4() {
        mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
        mTestLooper.dispatchAll();

        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
                HdmiControlManager.POWER_STATUS_ON);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
    }

    @Test
    public void setPowerStatus_transient_doesntSendBroadcast_1_4() {
        mHdmiCecPowerStatusController.setPowerStatus(
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
        mTestLooper.dispatchAll();

        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
    }

    @Test
    public void setPowerStatus_fast_transient_doesntSendBroadcast_1_4() {
        mHdmiCecPowerStatusController.setPowerStatus(
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false);
        mTestLooper.dispatchAll();

        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
    }

    @Test
    public void setPowerStatus_sendsBroadcast_2_0() {
        mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;

        mHdmiCecPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
        mTestLooper.dispatchAll();

        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
                HdmiControlManager.POWER_STATUS_ON);
        assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
    }

    @Test
    public void setPowerStatus_transient_sendsBroadcast_2_0() {
        mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;

        mHdmiCecPowerStatusController.setPowerStatus(
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
        mTestLooper.dispatchAll();

        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
        assertThat(mNativeWrapper.getResultMessages()).contains(reportPowerStatus);
    }

    @Test
    public void setPowerStatus_fast_transient_doesntSendBroadcast_2_0() {
        mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0;

        mHdmiCecPowerStatusController.setPowerStatus(
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, false);
        mTestLooper.dispatchAll();

        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
                Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST,
                HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPowerStatus);
    }
}
Loading