HDMI: Improve logic for sending and resending source changing messages

Rename SendCecCommandAction to ResendCecCommandAction. The action is now only created if the first attempt to send the message fails.
Use the action when sending a source changing message together with a callback.

Test: atest com.android.server.hdmi && atest android.hdmicec.cts.playback.HdmiCecRoutingControlTest#cect_11_2_2_4_InactiveSourceOnStandby
Bug: 289470244
Change-Id: Ib1b124751b9dbdd9ce3d2d1496c5451dc48b0309
This commit is contained in:
Paul Colta
2023-07-04 15:53:15 +02:00
parent f8e2f0f317
commit 307ec6b663
6 changed files with 171 additions and 50 deletions

View File

@@ -191,6 +191,11 @@ abstract class HdmiCecFeatureAction {
mService.sendCecCommand(cmd, callback);
}
protected final void sendCommandWithoutRetries(HdmiCecMessage cmd,
HdmiControlService.SendMessageCallback callback) {
mService.sendCecCommandWithoutRetries(cmd, callback);
}
protected final void addAndStartAction(HdmiCecFeatureAction action) {
mSource.addAndStartAction(action);
}

View File

@@ -1013,6 +1013,12 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
assertRunOnServiceThread();
mActions.add(action);
if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
if (action.getClass() == ResendCecCommandAction.class) {
Slog.i(TAG, "Not ready to start ResendCecCommandAction. "
+ "This action is cancelled.");
removeAction(action);
return;
}
Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
return;
}
@@ -1295,6 +1301,7 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
removeAction(AbsoluteVolumeAudioStatusAction.class);
removeAction(SetAudioVolumeLevelDiscoveryAction.class);
removeAction(ActiveSourceAction.class);
removeAction(ResendCecCommandAction.class);
mPendingActionClearedCallback =
new PendingActionClearedCallback() {

View File

@@ -1518,6 +1518,51 @@ public class HdmiControlService extends SystemService {
}
}
@ServiceThreadOnly
void sendCecCommand(HdmiCecMessage command) {
sendCecCommand(command, null);
}
@ServiceThreadOnly
void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
switch (command.getOpcode()) {
case Constants.MESSAGE_ACTIVE_SOURCE:
case Constants.MESSAGE_IMAGE_VIEW_ON:
case Constants.MESSAGE_INACTIVE_SOURCE:
case Constants.MESSAGE_ROUTING_CHANGE:
case Constants.MESSAGE_SET_STREAM_PATH:
case Constants.MESSAGE_TEXT_VIEW_ON:
sendCecCommandWithRetries(command, callback);
break;
default:
sendCecCommandWithoutRetries(command, callback);
}
}
/**
* Create a {@link ResendCecCommandAction} that will retry sending the CEC message if it fails.
* @param command command to be sent on the CEC bus.
* @param callback callback for handling the result of sending the command.
*/
@ServiceThreadOnly
private void sendCecCommandWithRetries(HdmiCecMessage command,
@Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
if (localDevice != null) {
sendCecCommandWithoutRetries(command, new SendMessageCallback() {
@Override
public void onSendCompleted(int result) {
if (result != SendMessageResult.SUCCESS) {
localDevice.addAndStartAction(new
ResendCecCommandAction(localDevice, command, callback));
}
}
});
}
}
/**
* Transmit a CEC command to CEC bus.
*
@@ -1525,7 +1570,8 @@ public class HdmiControlService extends SystemService {
* @param callback interface used to the result of send command
*/
@ServiceThreadOnly
void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
void sendCecCommandWithoutRetries(HdmiCecMessage command,
@Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
if (command.getValidationResult() == HdmiCecMessageValidator.OK
&& verifyPhysicalAddresses(command)) {
@@ -1538,37 +1584,6 @@ public class HdmiControlService extends SystemService {
}
}
@ServiceThreadOnly
void sendCecCommand(HdmiCecMessage command) {
assertRunOnServiceThread();
switch (command.getOpcode()) {
case Constants.MESSAGE_ACTIVE_SOURCE:
case Constants.MESSAGE_IMAGE_VIEW_ON:
case Constants.MESSAGE_INACTIVE_SOURCE:
case Constants.MESSAGE_ROUTING_CHANGE:
case Constants.MESSAGE_SET_STREAM_PATH:
case Constants.MESSAGE_TEXT_VIEW_ON:
sendCecCommandWithRetries(command);
break;
default:
sendCecCommand(command, null);
}
}
/**
* Create a {@link SendCecCommandAction} that will retry to send the CEC message in case it
* fails.
* @param command that has to be sent in the CEC bus.
*/
@ServiceThreadOnly
public void sendCecCommandWithRetries(HdmiCecMessage command) {
assertRunOnServiceThread();
HdmiCecLocalDevice localDevice = getAllCecLocalDevices().get(0);
if (localDevice != null) {
localDevice.addAndStartAction(new SendCecCommandAction(localDevice, command));
}
}
/**
* Send <Feature Abort> command on the given CEC message if possible.
* If the aborted message is invalid, then it wont send the message.

View File

@@ -16,23 +16,29 @@
package com.android.server.hdmi;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.hardware.tv.hdmi.cec.SendMessageResult;
import android.util.Slog;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
/**
* Action that sends a CEC command. If the message fails to be sent, it tries again for
* RETRANSMISSION_COUNT times.
* Action that retries RETRANSMISSION_COUNT times to send a CEC command when the first attempt to
* send the message failed.
*
* This action starts with a delay of SEND_COMMAND_RETRY_MS milliseconds.
*
* If this action can't be started it will be canceled and not deferred.
* See {@link HdmiCecLocalDevice#addAndStartAction}.
*/
public class SendCecCommandAction extends HdmiCecFeatureAction {
private static final String TAG = "SendCecCommandAction";
private static final int RETRANSMISSION_COUNT = 2;
public class ResendCecCommandAction extends HdmiCecFeatureAction {
private static final String TAG = "ResendCecCommandAction";
private static final int RETRANSMISSION_COUNT = 1;
private static final int STATE_WAIT_FOR_RESEND_COMMAND = 1;
static final int SEND_COMMAND_RETRY_MS = 300;
private final HdmiCecMessage mCommand;
private int mRetransmissionCount = 0;
private final SendMessageCallback mResultCallback;
private final SendMessageCallback mCallback = new SendMessageCallback(){
@Override
public void onSendCompleted(int result) {
@@ -41,20 +47,26 @@ public class SendCecCommandAction extends HdmiCecFeatureAction {
mState = STATE_WAIT_FOR_RESEND_COMMAND;
addTimer(mState, SEND_COMMAND_RETRY_MS);
} else {
if (mResultCallback != null) {
mResultCallback.onSendCompleted(result);
}
finish();
}
}
};
SendCecCommandAction(HdmiCecLocalDevice source, HdmiCecMessage command) {
ResendCecCommandAction(HdmiCecLocalDevice source, HdmiCecMessage command,
SendMessageCallback callback) {
super(source);
mCommand = command;
mResultCallback = callback;
mState = STATE_WAIT_FOR_RESEND_COMMAND;
addTimer(mState, SEND_COMMAND_RETRY_MS);
}
@Override
boolean start() {
Slog.d(TAG, "SendCecCommandAction started");
sendCommand(mCommand, mCallback);
Slog.d(TAG, "ResendCecCommandAction started");
return true;
}
@@ -66,8 +78,8 @@ public class SendCecCommandAction extends HdmiCecFeatureAction {
return;
}
if (mState == STATE_WAIT_FOR_RESEND_COMMAND) {
Slog.d(TAG, "sendCecCommand failed, retry");
sendCommand(mCommand, mCallback);
Slog.d(TAG, "sendCommandWithoutRetries failed, retry");
sendCommandWithoutRetries(mCommand, mCallback);
}
}

View File

@@ -17,11 +17,12 @@
package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.SendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
@@ -38,11 +39,11 @@ import org.junit.runners.JUnit4;
import java.util.Collections;
/** Tests for {@link SendCecCommandAction} */
/** Tests for {@link ResendCecCommandAction} */
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class SendCecCommandActionPlaybackTest {
public class ResendCecCommandActionPlaybackTest {
private static final String TAG = "SendCecCommandActionPlaybackTest";
private HdmiControlService mHdmiControlService;
private HdmiCecLocalDevice mPlaybackDevice;
@@ -51,6 +52,7 @@ public class SendCecCommandActionPlaybackTest {
private Looper mMyLooper;
private TestLooper mTestLooper = new TestLooper();
private int mPhysicalAddress;
private boolean mIsPowerStandby;
@Before
public void setUp() throws Exception {
@@ -63,7 +65,7 @@ public class SendCecCommandActionPlaybackTest {
@Override
boolean isPowerStandby() {
return false;
return mIsPowerStandby;
}
@Override
@@ -71,6 +73,7 @@ public class SendCecCommandActionPlaybackTest {
// do nothing
}
};
mIsPowerStandby = false;
mMyLooper = mTestLooper.getLooper();
mHdmiControlService.setIoLooper(mMyLooper);
@@ -148,6 +151,35 @@ public class SendCecCommandActionPlaybackTest {
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
}
@Test
public void sendCecCommand_inactiveSource_onStandby_powerControlModeNone_sendMessage() {
mPlaybackDevice.mService.getHdmiCecConfig().setStringValue(
HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
HdmiControlManager.POWER_CONTROL_MODE_NONE);
mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
mPhysicalAddress, "SendCecCommandActionPlaybackTest");
mIsPowerStandby = true;
mPlaybackDevice.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
}
@Test
public void sendCecCommand_inactiveSource_onStandby_initiatedByCec_sendMessage() {
mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
mPhysicalAddress, "SendCecCommandActionPlaybackTest");
mIsPowerStandby = true;
mPlaybackDevice.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
}
@Test
public void sendCecCommand_routingChange_sendMessageFails_resendMessage() {
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_ROUTING_CHANGE,
@@ -293,4 +325,54 @@ public class SendCecCommandActionPlaybackTest {
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportPhysicalAddress);
}
@Test
public void sendCecCommand_inactiveSource_sendMessageFails_afterStandby_noResendMessage() {
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
SendMessageResult.BUSY);
mPlaybackDevice.setActiveSource(mPlaybackDevice.getDeviceInfo().getLogicalAddress(),
mPhysicalAddress, "SendCecCommandActionPlaybackTest");
mIsPowerStandby = true;
mPlaybackDevice.onStandby(true, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
mNativeWrapper.clearResultMessages();
mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
}
@Test
public void sendCecCommand_onStandby_removeAction_noResendMessage() {
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_INACTIVE_SOURCE,
SendMessageResult.BUSY);
mTestLooper.dispatchAll();
HdmiCecMessage inactiveSourceMessage = HdmiCecMessageBuilder.buildInactiveSource(
mPlaybackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
mHdmiControlService.sendCecCommand(inactiveSourceMessage);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(inactiveSourceMessage);
mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
mNativeWrapper.clearResultMessages();
mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
mTestLooper.dispatchAll();
mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
mTestLooper.moveTimeForward(SEND_COMMAND_RETRY_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(inactiveSourceMessage);
}
}

View File

@@ -17,7 +17,7 @@
package com.android.server.hdmi;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.SendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -38,11 +38,11 @@ import org.junit.runners.JUnit4;
import java.util.Collections;
/** Tests for {@link SendCecCommandAction} */
/** Tests for {@link ResendCecCommandAction} */
@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class SendCecCommandActionTvTest {
public class ResendCecCommandActionTvTest {
private static final String TAG = "SendCecCommandActionTvTest";
private HdmiControlService mHdmiControlService;
private HdmiCecLocalDevice mTvDevice;