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:
@@ -102,7 +102,7 @@ final class DeviceSelectActionFromPlayback extends HdmiCecFeatureAction {
|
||||
mIsCec20 = isCec20;
|
||||
}
|
||||
|
||||
private int getTargetAddress() {
|
||||
int getTargetAddress() {
|
||||
return mTarget.getLogicalAddress();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user