From d2819821479dbed05fc4c2363ab4f1becc4a131f Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Sep 2021 11:17:51 +0000 Subject: [PATCH] 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 --- .../hdmi/DeviceSelectActionFromPlayback.java | 2 +- .../server/hdmi/HdmiCecController.java | 3 +- .../server/hdmi/HdmiCecLocalDevice.java | 29 ++++--- .../hdmi/HdmiCecLocalDevicePlayback.java | 40 ++++++++- .../server/hdmi/HdmiCecLocalDeviceTv.java | 9 +- .../server/hdmi/HotplugDetectionAction.java | 82 +++++++++++-------- .../server/hdmi/PowerStatusMonitorAction.java | 4 +- .../hdmi/HdmiCecLocalDevicePlaybackTest.java | 57 +++++++++++++ .../hdmi/PowerStatusMonitorActionTest.java | 2 +- 9 files changed, 167 insertions(+), 61 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromPlayback.java b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromPlayback.java index b2338e6c76160..1bacca683c052 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectActionFromPlayback.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectActionFromPlayback.java @@ -102,7 +102,7 @@ final class DeviceSelectActionFromPlayback extends HdmiCecFeatureAction { mIsCec20 = isCec20; } - private int getTargetAddress() { + int getTargetAddress() { return mTarget.getLogicalAddress(); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 6736d2a227dda..cc864307daf10 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -324,7 +324,8 @@ final class HdmiCecController { /** * Return the physical address of the device. * - *

Declared as package-private. accessed by {@link HdmiControlService} only. + *

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 diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index f94d2207381f0..ab8217a9f9cd9 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -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) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 744436d41adb1..d6ac25a72a6e4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -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 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 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(); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 8d0a7bd2cc8b0..aba7d3927f473 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -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) { diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index 6fd7a72fc2874..5d23fa45e41f4 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -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. * - *

Check all devices every 15 secs except for system audio. + *

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. + * + *

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 actions = getActions(DeviceSelectActionFromTv.class); - if (actions.isEmpty()) { - return; + List 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 actionsFromPlayback = getActions( + DeviceSelectActionFromPlayback.class); + for (DeviceSelectActionFromPlayback action : actionsFromPlayback) { + if (action.getTargetAddress() == address) { + removeAction(DeviceSelectActionFromTv.class); + } } } diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java index 66fc0d9c1760a..acf705a056065 100644 --- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java @@ -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 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); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 8a84c6f9c6e84..055459c867055 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -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 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 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, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index a12aa295bcd65..2b49095776d70 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -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 {