HDMICEC: Implement active tracking of CEC Network for Playback devices

Poll all the non-local logical addresses when the device is either turned on or when the CEC setting is enabled. After that the addresses are polled once every 1 minute.
Implement method inside HdmiCecLocalDevicePlayback that launches a DeviceDiscoveryAction with a HotplugDetectionAction inside its callback.
Add unit tests for DeviceDiscoveryAction and HotplugDetectionAction for HdmiCecLocalDevicePlayback.

Bug: 199057329
Test: make && atest CtsHdmiCecHostTestCases
Change-Id: I1133cdbced4a1e40e299a95bb9ced565c8059774
This commit is contained in:
Paul
2021-09-20 11:17:51 +00:00
committed by Paul Colța
parent c0c38ddd2a
commit d281982147
9 changed files with 167 additions and 61 deletions

View File

@@ -102,7 +102,7 @@ final class DeviceSelectActionFromPlayback extends HdmiCecFeatureAction {
mIsCec20 = isCec20;
}
private int getTargetAddress() {
int getTargetAddress() {
return mTarget.getLogicalAddress();
}

View File

@@ -324,7 +324,8 @@ final class HdmiCecController {
/**
* Return the physical address of the device.
*
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
* <p>Declared as package-private. accessed by {@link HdmiControlService} and
* {@link HdmiCecNetwork} only.
*
* @return CEC physical address of the device. The range of success address
* is between 0x0000 and 0xFFFF. If failed it returns -1

View File

@@ -274,6 +274,13 @@ abstract class HdmiCecLocalDevice {
return false;
}
// Clear all device info.
@ServiceThreadOnly
void clearDeviceInfoList() {
assertRunOnServiceThread();
mService.getHdmiCecNetwork().clearDeviceList();
}
@ServiceThreadOnly
@Constants.HandleMessageResult
protected final int onMessage(HdmiCecMessage message) {
@@ -787,10 +794,10 @@ abstract class HdmiCecLocalDevice {
byte[] params = message.getParams();
return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
&& (params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION
|| params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
}
@Constants.HandleMessageResult
@@ -1243,13 +1250,13 @@ abstract class HdmiCecLocalDevice {
|| logicalAddress == mDeviceInfo.getLogicalAddress()) {
// Don't send key event to invalid device or itself.
Slog.w(
TAG,
"Discard volume key event: "
+ keyCode
+ ", pressed:"
+ isPressed
+ ", receiverAddr="
+ logicalAddress);
TAG,
"Discard volume key event: "
+ keyCode
+ ", pressed:"
+ isPressed
+ ", receiverAddr="
+ logicalAddress);
} else if (!action.isEmpty()) {
action.get(0).processKeyEvent(keyCode, isPressed);
} else if (isPressed) {

View File

@@ -105,9 +105,42 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
}
});
}
launchDeviceDiscovery();
startQueuedActions();
}
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
clearDeviceInfoList();
DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
new DeviceDiscoveryAction.DeviceDiscoveryCallback() {
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
mService.getHdmiCecNetwork().addCecDevice(info);
}
// Since we removed all devices when it starts and device discovery action
// does not poll local devices, we should put device info of local device
// manually here.
for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
synchronized (device.mLock) {
mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
}
}
List<HotplugDetectionAction> hotplugActions =
getActions(HotplugDetectionAction.class);
if (hotplugActions.isEmpty()) {
addAndStartAction(
new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this));
}
}
});
addAndStartAction(action);
}
@Override
@ServiceThreadOnly
protected int getPreferredAddress() {
@@ -450,9 +483,12 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
@Override
@ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
super.disableDevice(initiatedByCec, callback);
assertRunOnServiceThread();
removeAction(DeviceDiscoveryAction.class);
removeAction(HotplugDetectionAction.class);
removeAction(NewDeviceAction.class);
super.disableDevice(initiatedByCec, callback);
clearDeviceInfoList();
checkIfPendingActionsCleared();
}

View File

@@ -685,7 +685,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
mService.getHdmiCecNetwork().addCecDevice(info);
}
// Since we removed all devices when it's start and
// Since we removed all devices when it starts and
// device discovery action does not poll local devices,
// we should put device info of local device manually here
for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
@@ -734,13 +734,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
}
// Clear all device info.
@ServiceThreadOnly
private void clearDeviceInfoList() {
assertRunOnServiceThread();
mService.getHdmiCecNetwork().clearDeviceList();
}
@ServiceThreadOnly
// Seq #32
void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {

View File

@@ -28,15 +28,18 @@ import java.util.List;
* Feature action that handles hot-plug detection mechanism.
* Hot-plug event is initiated by timer after device discovery action.
*
* <p>Check all devices every 15 secs except for system audio.
* <p>TV checks all devices every 15 secs except for system audio.
* If system audio is on, check hot-plug for audio system every 5 secs.
* For other devices, keep 15 secs period.
*
* <p>Playback devices check all devices every 1 minute.
*/
// Seq #3
final class HotplugDetectionAction extends HdmiCecFeatureAction {
private static final String TAG = "HotPlugDetectionAction";
private static final int POLLING_INTERVAL_MS = 5000;
private static final int POLLING_INTERVAL_MS_FOR_TV = 5000;
public static final int POLLING_INTERVAL_MS_FOR_PLAYBACK = 60000;
private static final int TIMEOUT_COUNT = 3;
private static final int AVR_COUNT_MAX = 3;
@@ -55,6 +58,8 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
// is detected {@code AVR_COUNT_MAX} times in a row.
private int mAvrStatusCount = 0;
private final boolean mIsTvDevice = localDevice().mService.isTvDevice();
/**
* Constructor
*
@@ -64,16 +69,21 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
super(source);
}
private int getPollingInterval() {
return mIsTvDevice ? POLLING_INTERVAL_MS_FOR_TV : POLLING_INTERVAL_MS_FOR_PLAYBACK;
}
@Override
boolean start() {
Slog.v(TAG, "Hot-plug dection started.");
Slog.v(TAG, "Hot-plug detection started.");
mState = STATE_WAIT_FOR_NEXT_POLLING;
mTimeoutCount = 0;
// Start timer without polling.
// The first check for all devices will be initiated 15 seconds later.
addTimer(mState, POLLING_INTERVAL_MS);
// The first check for all devices will be initiated 15 seconds later for TV panels and 60
// seconds later for playback devices.
addTimer(mState, getPollingInterval());
return true;
}
@@ -90,13 +100,24 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
}
if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
pollDevices();
if (mIsTvDevice) {
mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
if (mTimeoutCount == 0) {
pollAllDevices();
} else if (tv().isSystemAudioActivated()) {
pollAudioSystem();
}
addTimer(mState, POLLING_INTERVAL_MS_FOR_TV);
return;
}
pollAllDevices();
addTimer(mState, POLLING_INTERVAL_MS_FOR_PLAYBACK);
}
}
/**
* Start device polling immediately.
* Start device polling immediately. This method is called only by
* {@link HdmiCecLocalDeviceTv#onHotplug}.
*/
void pollAllDevicesNow() {
// Clear existing timer to avoid overlapped execution
@@ -106,21 +127,7 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
mState = STATE_WAIT_FOR_NEXT_POLLING;
pollAllDevices();
addTimer(mState, POLLING_INTERVAL_MS);
}
// This method is called every 5 seconds.
private void pollDevices() {
// All device check called every 15 seconds.
if (mTimeoutCount == 0) {
pollAllDevices();
} else {
if (tv().isSystemAudioActivated()) {
pollAudioSystem();
}
}
addTimer(mState, POLLING_INTERVAL_MS);
addTimer(mState, getPollingInterval());
}
private void pollAllDevices() {
@@ -156,7 +163,7 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
BitSet removed = complement(currentInfos, polledResult);
int index = -1;
while ((index = removed.nextSetBit(index + 1)) != -1) {
if (index == Constants.ADDR_AUDIO_SYSTEM) {
if (mIsTvDevice && index == Constants.ADDR_AUDIO_SYSTEM) {
HdmiDeviceInfo avr = tv().getAvrDeviceInfo();
if (avr != null && tv().isConnected(avr.getPortId())) {
++mAvrStatusCount;
@@ -221,11 +228,12 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
}
private void removeDevice(int removedAddress) {
mayChangeRoutingPath(removedAddress);
if (mIsTvDevice) {
mayChangeRoutingPath(removedAddress);
mayCancelOneTouchRecord(removedAddress);
mayDisableSystemAudioAndARC(removedAddress);
}
mayCancelDeviceSelect(removedAddress);
mayCancelOneTouchRecord(removedAddress);
mayDisableSystemAudioAndARC(removedAddress);
localDevice().mService.getHdmiCecNetwork().removeCecDevice(localDevice(), removedAddress);
}
@@ -237,15 +245,19 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
}
private void mayCancelDeviceSelect(int address) {
List<DeviceSelectActionFromTv> actions = getActions(DeviceSelectActionFromTv.class);
if (actions.isEmpty()) {
return;
List<DeviceSelectActionFromTv> actionsFromTv = getActions(DeviceSelectActionFromTv.class);
for (DeviceSelectActionFromTv action : actionsFromTv) {
if (action.getTargetAddress() == address) {
removeAction(DeviceSelectActionFromTv.class);
}
}
// Should have only one Device Select Action
DeviceSelectActionFromTv action = actions.get(0);
if (action.getTargetAddress() == address) {
removeAction(DeviceSelectActionFromTv.class);
List<DeviceSelectActionFromPlayback> actionsFromPlayback = getActions(
DeviceSelectActionFromPlayback.class);
for (DeviceSelectActionFromPlayback action : actionsFromPlayback) {
if (action.getTargetAddress() == address) {
removeAction(DeviceSelectActionFromTv.class);
}
}
}

View File

@@ -41,7 +41,7 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
private static final int INVALID_POWER_STATUS = POWER_STATUS_UNKNOWN - 1;
// Monitoring interval (60s)
private static final int MONITORING_INTERNAL_MS = 60000;
private static final int MONITORING_INTERVAL_MS = 60000;
// Timeout once sending <Give Device Power Status>
private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
@@ -142,7 +142,7 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
// Add both timers, monitoring and timeout.
addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITORING_INTERNAL_MS);
addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITORING_INTERVAL_MS);
addTimer(STATE_WAIT_FOR_REPORT_POWER_STATUS, REPORT_POWER_STATUS_TIMEOUT_MS);
}

View File

@@ -29,6 +29,7 @@ import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -54,6 +55,8 @@ import java.util.concurrent.TimeUnit;
/** Tests for {@link HdmiCecLocalDevicePlayback} class. */
public class HdmiCecLocalDevicePlaybackTest {
private static final int TIMEOUT_MS = HdmiConfig.TIMEOUT_MS + 1;
private static final int HOTPLUG_INTERVAL =
HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_PLAYBACK;
private static final int PORT_1 = 1;
private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo(
@@ -1645,6 +1648,60 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(systemAudioModeRequest);
}
@Test
public void onAddressAllocated_invokesDeviceDiscovery() {
mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
// Check for <Give Physical Address> being sent to available device (ADDR_PLAYBACK_2).
// This message is sent as part of the DeviceDiscoveryAction to available devices.
HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
Constants.ADDR_PLAYBACK_1,
Constants.ADDR_PLAYBACK_2);
assertThat(mNativeWrapper.getResultMessages()).contains(givePhysicalAddress);
}
@Test
public void hotplugDetectionAction_addDevice() {
int otherPlaybackLogicalAddress = mPlaybackLogicalAddress == Constants.ADDR_PLAYBACK_2
? Constants.ADDR_PLAYBACK_1 : Constants.ADDR_PLAYBACK_2;
mNativeWrapper.setPollAddressResponse(otherPlaybackLogicalAddress,
SendMessageResult.NACK);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
mNativeWrapper.setPollAddressResponse(otherPlaybackLogicalAddress,
SendMessageResult.SUCCESS);
mTestLooper.moveTimeForward(HOTPLUG_INTERVAL);
mTestLooper.dispatchAll();
// Check for <Give Physical Address> being sent to the newly discovered device.
// This message is sent as part of the HotplugDetectionAction to available devices.
HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
mPlaybackLogicalAddress, otherPlaybackLogicalAddress);
assertThat(mNativeWrapper.getResultMessages()).contains(givePhysicalAddress);
}
@Test
public void hotplugDetectionAction_removeDevice() {
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
HdmiDeviceInfo infoPlayback = new HdmiDeviceInfo(
Constants.ADDR_PLAYBACK_2, 0x1234, PORT_1,
HdmiDeviceInfo.DEVICE_PLAYBACK, 0x1234, "Playback 2",
HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback);
// This logical address (ADDR_PLAYBACK_2) won't acknowledge the poll message sent by the
// HotplugDetectionAction so it shall be removed.
mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.NACK);
mTestLooper.moveTimeForward(HOTPLUG_INTERVAL);
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false)).isEmpty();
}
@Test
public void getActiveSource_noActiveSource() {
mHdmiControlService.setActiveSource(Constants.ADDR_UNREGISTERED,

View File

@@ -46,7 +46,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/** Tests for {@link ActiveSourceAction} */
/** Tests for {@link PowerStatusMonitorAction} */
@SmallTest
@RunWith(JUnit4.class)
public class PowerStatusMonitorActionTest {