Loading services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +12 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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(); Loading services/core/java/com/android/server/hdmi/HdmiControlService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -5137,4 +5139,8 @@ public class HdmiControlService extends SystemService { tv().startArcAction(enabled, callback); } } protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() { return hdmiControlEnhancedBehavior(); } } services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java 0 → 100644 +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); } } } } services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)); Loading Loading @@ -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(); Loading Loading
services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +12 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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(); Loading
services/core/java/com/android/server/hdmi/HdmiControlService.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -5137,4 +5139,8 @@ public class HdmiControlService extends SystemService { tv().startArcAction(enabled, callback); } } protected boolean isHdmiControlEnhancedBehaviorFlagEnabled() { return hdmiControlEnhancedBehavior(); } }
services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java 0 → 100644 +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); } } } }
services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)); Loading Loading @@ -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(); Loading