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

Commit 7bd4efd9 authored by Paul Colta's avatar Paul Colta Committed by Android (Google) Code Review
Browse files

Merge "HDMI: Send device to sleep if TV reported it turned off." into main

parents ac01554b d9d7d52c
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -171,6 +171,17 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
                            addAndStartAction(
                                    new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this));
                        }

                        if (mService.isHdmiControlEnhancedBehaviorFlagEnabled()) {
                            List<PowerStatusMonitorActionFromPlayback>
                                    powerStatusMonitorActionsFromPlayback =
                                    getActions(PowerStatusMonitorActionFromPlayback.class);
                            if (powerStatusMonitorActionsFromPlayback.isEmpty()) {
                                addAndStartAction(
                                        new PowerStatusMonitorActionFromPlayback(
                                                HdmiCecLocalDevicePlayback.this));
                            }
                        }
                    }
                });
        addAndStartAction(action);
@@ -686,6 +697,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
        removeAction(DeviceDiscoveryAction.class);
        removeAction(HotplugDetectionAction.class);
        removeAction(NewDeviceAction.class);
        removeAction(PowerStatusMonitorActionFromPlayback.class);
        super.disableDevice(initiatedByCec, callback);
        clearDeviceInfoList();
        checkIfPendingActionsCleared();
+6 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.hdmi;

import static android.media.tv.flags.Flags.hdmiControlEnhancedBehavior;

import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED;
@@ -5137,4 +5139,8 @@ public class HdmiControlService extends SystemService {
            tv().startArcAction(enabled, callback);
        }
    }

    protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() {
        return hdmiControlEnhancedBehavior();
    }
}
+111 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;

import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

/**
 * This action is used by playback devices to query TV's power status such that they can go to
 * standby when the TV reports power off.
 */
public class PowerStatusMonitorActionFromPlayback extends HdmiCecFeatureAction {
    private static final String TAG = "PowerStatusMonitorActionFromPlayback";

    // State that waits for <Report Power Status> once sending <Give Device Power Status>
    // to all external devices.
    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
    // State that waits for next monitoring.
    private static final int STATE_WAIT_FOR_NEXT_MONITORING = 2;
    // Monitoring interval (60s)
    @VisibleForTesting
    protected static final int MONITORING_INTERVAL_MS = 60000;
    // Timeout once sending <Give Device Power Status>
    private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
    // Maximum number of retries in case the <Give Device Power Status> failed being sent or times
    // out.
    private static final int GIVE_POWER_STATUS_FOR_SOURCE_RETRIES = 5;
    private int mPowerStatusRetries = 0;

    PowerStatusMonitorActionFromPlayback(HdmiCecLocalDevice source) {
        super(source);
    }

    @Override
    boolean start() {
        // Start after timeout since the device just finished allocation.
        mState = STATE_WAIT_FOR_NEXT_MONITORING;
        addTimer(mState, MONITORING_INTERVAL_MS);
        return true;
    }

    @Override
    boolean processCommand(HdmiCecMessage cmd) {
        if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
                && cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS
                && cmd.getSource() == Constants.ADDR_TV) {
            return handleReportPowerStatusFromTv(cmd);
        }
        return false;
    }

    private boolean handleReportPowerStatusFromTv(HdmiCecMessage cmd) {
        int powerStatus = cmd.getParams()[0] & 0xFF;
        if (powerStatus == POWER_STATUS_STANDBY) {
            Slog.d(TAG, "TV reported it turned off, going to sleep.");
            source().getService().standby();
            return true;
        }
        return false;
    }

    @Override
    void handleTimerEvent(int state) {
        switch (mState) {
            case STATE_WAIT_FOR_NEXT_MONITORING:
                mPowerStatusRetries = 0;
                queryPowerStatus();
                break;
            case STATE_WAIT_FOR_REPORT_POWER_STATUS:
                handleTimeout();
                break;
        }
    }

    private void queryPowerStatus() {
        sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
                        Constants.ADDR_TV));

        mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
        addTimer(mState, REPORT_POWER_STATUS_TIMEOUT_MS);
    }

    private void handleTimeout() {
        if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS) {
            if (mPowerStatusRetries++ < GIVE_POWER_STATUS_FOR_SOURCE_RETRIES) {
                queryPowerStatus();
            } else {
                mPowerStatusRetries = 0;
                mState = STATE_WAIT_FOR_NEXT_MONITORING;
                addTimer(mState, MONITORING_INTERVAL_MS);
            }
        }
    }
}
+44 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static com.android.server.hdmi.HdmiCecLocalDevicePlayback.POPUP_AFTER_ACT
import static com.android.server.hdmi.HdmiCecLocalDevicePlayback.STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.PowerStatusMonitorActionFromPlayback.MONITORING_INTERVAL_MS;

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

@@ -145,6 +146,11 @@ public class HdmiCecLocalDevicePlaybackTest {
                    protected void sendBroadcastAsUser(@RequiresPermission Intent intent) {
                        // do nothing
                    }

                    @Override
                    protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() {
                        return true;
                    }
                };

        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
@@ -2556,6 +2562,44 @@ public class HdmiCecLocalDevicePlaybackTest {
        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
    }

    @Test
    public void powerStatusMonitorActionFromPlayback_TvReportPowerOff_goToSleep() {
        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
        mTestLooper.dispatchAll();

        assertThat(mHdmiCecLocalDevicePlayback.getActions(
                PowerStatusMonitorActionFromPlayback.class)).hasSize(1);
        assertThat(mPowerManager.isInteractive()).isTrue();
        mNativeWrapper.clearResultMessages();
        mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS);
        mTestLooper.dispatchAll();

        HdmiCecMessage givePowerStatus =
                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mPlaybackLogicalAddress,
                        Constants.ADDR_TV);
        HdmiCecMessage reportPowerStatusTvOn =
                HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress,
                        HdmiControlManager.POWER_STATUS_ON);
        HdmiCecMessage reportPowerStatusTvStandby =
                HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress,
                        HdmiControlManager.POWER_STATUS_STANDBY);

        assertThat(mNativeWrapper.getResultMessages().contains(givePowerStatus)).isTrue();
        mNativeWrapper.onCecMessage(reportPowerStatusTvOn);
        mTestLooper.dispatchAll();

        assertThat(mPowerManager.isInteractive()).isTrue();
        mNativeWrapper.clearResultMessages();
        mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS);
        mTestLooper.dispatchAll();

        assertThat(mNativeWrapper.getResultMessages().contains(givePowerStatus)).isTrue();
        mNativeWrapper.onCecMessage(reportPowerStatusTvStandby);
        mTestLooper.dispatchAll();

        assertThat(mPowerManager.isInteractive()).isFalse();
    }

    private void skipActiveSourceLostUi(long idleDuration) {
        mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
        mTestLooper.dispatchAll();