Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3d7f0603 authored by Paul Colta's avatar Paul Colta
Browse files

HDMI: Avoid creating multiple local devices with the same type

Do not clear the local devices list when the address allocation
starts, but replace the local devices in the network as they are
allocated.
After the allocation, local devices with types that weren't
allocated are removed.
E.g.: Step 1. Dynamic soundbar mode is enabled -> two local devices are allocated (playback + audio system).
Step 2. Dynamic soundbar mode is disabled -> only one local device is allocated (playback), therefore the local device audio system can be removed.

Test: atest HdmiControlServiceTest
Bug: 297312959
Bug: 297349911
Change-Id: I2778b3f5e2581f79a4d899af8025f4b248fe8375
parent b53fbe73
Loading
Loading
Loading
Loading
+22 −0
Original line number Original line Diff line number Diff line
@@ -867,6 +867,28 @@ public class HdmiCecNetwork {
        clearLocalDevices();
        clearLocalDevices();
    }
    }


    @ServiceThreadOnly
    void removeUnusedLocalDevices(ArrayList<HdmiCecLocalDevice> allocatedDevices) {
        ArrayList<Integer> deviceTypesToRemove = new ArrayList<>();
        for (int i = 0; i < mLocalDevices.size(); i++) {
            int deviceType = mLocalDevices.keyAt(i);
            boolean shouldRemoveLocalDevice = allocatedDevices.stream().noneMatch(
                    localDevice -> localDevice.getDeviceInfo() != null
                    && localDevice.getDeviceInfo().getDeviceType() == deviceType);
            if (shouldRemoveLocalDevice) {
                deviceTypesToRemove.add(deviceType);
            }
        }
        for (Integer deviceType : deviceTypesToRemove) {
            mLocalDevices.remove(deviceType);
        }
    }

    @ServiceThreadOnly
    void removeLocalDeviceWithType(int deviceType) {
        mLocalDevices.remove(deviceType);
    }

    @ServiceThreadOnly
    @ServiceThreadOnly
    public void clearDeviceList() {
    public void clearDeviceList() {
        assertRunOnServiceThread();
        assertRunOnServiceThread();
+18 −4
Original line number Original line Diff line number Diff line
@@ -1295,9 +1295,6 @@ public class HdmiControlService extends SystemService {
            localDevice.init();
            localDevice.init();
            localDevices.add(localDevice);
            localDevices.add(localDevice);
        }
        }
        // It's now safe to flush existing local devices from mCecController since they were
        // already moved to 'localDevices'.
        clearCecLocalDevices();
        mHdmiCecNetwork.clearDeviceList();
        mHdmiCecNetwork.clearDeviceList();
        allocateLogicalAddress(localDevices, initiatedBy);
        allocateLogicalAddress(localDevices, initiatedBy);
    }
    }
@@ -1326,6 +1323,7 @@ public class HdmiControlService extends SystemService {
                            if (logicalAddress == Constants.ADDR_UNREGISTERED) {
                            if (logicalAddress == Constants.ADDR_UNREGISTERED) {
                                Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType
                                Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType
                                        + "]");
                                        + "]");
                                mHdmiCecNetwork.removeLocalDeviceWithType(deviceType);
                            } else {
                            } else {
                                // Set POWER_STATUS_ON to all local devices because they share
                                // Set POWER_STATUS_ON to all local devices because they share
                                // lifetime
                                // lifetime
@@ -1334,6 +1332,8 @@ public class HdmiControlService extends SystemService {
                                        deviceType,
                                        deviceType,
                                        HdmiControlManager.POWER_STATUS_ON, getCecVersion());
                                        HdmiControlManager.POWER_STATUS_ON, getCecVersion());
                                localDevice.setDeviceInfo(deviceInfo);
                                localDevice.setDeviceInfo(deviceInfo);
                                // If a local device of the same type already exists, it will be
                                // replaced.
                                mHdmiCecNetwork.addLocalDevice(deviceType, localDevice);
                                mHdmiCecNetwork.addLocalDevice(deviceType, localDevice);
                                mHdmiCecNetwork.addCecDevice(localDevice.getDeviceInfo());
                                mHdmiCecNetwork.addCecDevice(localDevice.getDeviceInfo());
                                mCecController.addLogicalAddress(logicalAddress);
                                mCecController.addLogicalAddress(logicalAddress);
@@ -1349,6 +1349,10 @@ public class HdmiControlService extends SystemService {
                                    // since we reallocate the logical address only.
                                    // since we reallocate the logical address only.
                                    onInitializeCecComplete(initiatedBy);
                                    onInitializeCecComplete(initiatedBy);
                                }
                                }
                                // We remove local devices here, instead of before the start of
                                // address allocation, to prevent multiple local devices of the
                                // same type from existing simultaneously.
                                mHdmiCecNetwork.removeUnusedLocalDevices(allocatedDevices);
                                mAddressAllocated = true;
                                mAddressAllocated = true;
                                notifyAddressAllocated(allocatedDevices, initiatedBy);
                                notifyAddressAllocated(allocatedDevices, initiatedBy);
                                // Reinvoke the saved display status callback once the local
                                // Reinvoke the saved display status callback once the local
@@ -1368,9 +1372,19 @@ public class HdmiControlService extends SystemService {
        }
        }
    }
    }


    /**
     * Notifies local devices that address allocation finished.
     * @param devices - list of local devices allocated.
     * @param initiatedBy - reason for the address allocation.
     */
    @VisibleForTesting
    @ServiceThreadOnly
    @ServiceThreadOnly
    private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
    public void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
        assertRunOnServiceThread();
        assertRunOnServiceThread();
        if (devices == null || devices.isEmpty()) {
            Slog.w(TAG, "No local device to notify.");
            return;
        }
        List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer();
        List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer();
        for (HdmiCecLocalDevice device : devices) {
        for (HdmiCecLocalDevice device : devices) {
            int address = device.getDeviceInfo().getLogicalAddress();
            int address = device.getDeviceInfo().getLogicalAddress();
+218 −1
Original line number Original line Diff line number Diff line
@@ -21,8 +21,15 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;


import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;
import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_HOTPLUG;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_SCREEN_ON;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_SOUNDBAR_MODE;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;


import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;
@@ -51,6 +58,7 @@ import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
import android.os.Binder;
import android.os.Looper;
import android.os.Looper;
import android.os.RemoteException;
import android.os.RemoteException;
@@ -71,6 +79,7 @@ import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Optional;
import java.util.Optional;
import java.util.concurrent.TimeUnit;


/**
/**
 * Tests for {@link HdmiControlService} class.
 * Tests for {@link HdmiControlService} class.
@@ -137,7 +146,7 @@ public class HdmiControlServiceTest {


        mLocalDevices.add(mAudioSystemDeviceSpy);
        mLocalDevices.add(mAudioSystemDeviceSpy);
        mLocalDevices.add(mPlaybackDeviceSpy);
        mLocalDevices.add(mPlaybackDeviceSpy);
        mHdmiPortInfo = new HdmiPortInfo[4];
        mHdmiPortInfo = new HdmiPortInfo[5];
        mHdmiPortInfo[0] =
        mHdmiPortInfo[0] =
                new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x2100)
                new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x2100)
                        .setCecSupported(true)
                        .setCecSupported(true)
@@ -166,6 +175,13 @@ public class HdmiControlServiceTest {
                        .setArcSupported(false)
                        .setArcSupported(false)
                        .setEarcSupported(false)
                        .setEarcSupported(false)
                        .build();
                        .build();
        mHdmiPortInfo[4] =
                new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000)
                        .setCecSupported(true)
                        .setMhlSupported(false)
                        .setArcSupported(false)
                        .setEarcSupported(false)
                        .build();
        mNativeWrapper.setPortInfo(mHdmiPortInfo);
        mNativeWrapper.setPortInfo(mHdmiPortInfo);
        mHdmiControlServiceSpy.initService();
        mHdmiControlServiceSpy.initService();
        mWakeLockSpy = spy(new FakePowerManagerWrapper.FakeWakeLockWrapper());
        mWakeLockSpy = spy(new FakePowerManagerWrapper.FakeWakeLockWrapper());
@@ -1395,6 +1411,207 @@ public class HdmiControlServiceTest {
        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
    }
    }


    @Test
    public void triggerMultipleAddressAllocations_uniqueLocalDevicePerDeviceType() {
        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
        mTestLooper.dispatchAll();
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        // Wake up process that will trigger the address allocation to start.
        mHdmiControlServiceSpy.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SCREEN_ON));
        Mockito.clearInvocations(mHdmiControlServiceSpy);
        mTestLooper.dispatchAll();

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // Hotplug In will trigger the address allocation to start.
        mHdmiControlServiceSpy.onHotplug(4, true);
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_HOTPLUG));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The first allocation finished. The second allocation is still in progress.
        HdmiCecLocalDevicePlayback firstAllocatedPlayback = mHdmiControlServiceSpy.playback();
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SCREEN_ON));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The second allocation finished.
        HdmiCecLocalDevicePlayback secondAllocatedPlayback = mHdmiControlServiceSpy.playback();
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_HOTPLUG));
        // Local devices have the same identity.
        assertTrue(firstAllocatedPlayback == secondAllocatedPlayback);
    }

    @Test
    public void triggerMultipleAddressAllocations_keepLastAllocatedAddress() {
        // First logical address for playback is free.
        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK);
        mTestLooper.dispatchAll();

        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
        mTestLooper.dispatchAll();
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        // Wake up process that will trigger the address allocation to start.
        mHdmiControlServiceSpy.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SCREEN_ON));
        Mockito.clearInvocations(mHdmiControlServiceSpy);
        mTestLooper.dispatchAll();

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();

        // First logical address for playback is busy.
        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
        mTestLooper.dispatchAll();

        mHdmiControlServiceSpy.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SCREEN_ON));
        Mockito.clearInvocations(mHdmiControlServiceSpy);
        mTestLooper.dispatchAll();

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The first allocation finished. The second allocation is still in progress.
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SCREEN_ON));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The second allocation finished. Second logical address for playback is used.
        HdmiCecLocalDevicePlayback allocatedPlayback = mHdmiControlServiceSpy.playback();
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SCREEN_ON));
        assertThat(allocatedPlayback.getDeviceInfo().getLogicalAddress())
                .isEqualTo(ADDR_PLAYBACK_2);
    }

    @Test
    public void triggerMultipleAddressAllocations_toggleSoundbarMode_addThenRemoveAudioSystem() {
        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
        mTestLooper.dispatchAll();
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        // Enabling Dynamic soundbar mode will trigger address allocation.
        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
        mTestLooper.dispatchAll();
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // Disabling Dynamic soundbar mode will trigger another address allocation.
        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                HdmiControlManager.SOUNDBAR_MODE_DISABLED);
        mTestLooper.dispatchAll();
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The first allocation finished. The second allocation is still in progress.
        // The audio system is present in the network.
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The second allocation finished. The audio system is not present in the network.
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
    }

    @Test
    public void triggerMultipleAddressAllocations_toggleSoundbarMode_removeThenAddAudioSystem() {
        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
        // Enable the setting and check if the audio system local device is found in the network.
        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
        mTestLooper.dispatchAll();
        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();

        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
        mTestLooper.dispatchAll();
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        // Disabling Dynamic soundbar mode will trigger address allocation.
        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                HdmiControlManager.SOUNDBAR_MODE_DISABLED);
        mTestLooper.dispatchAll();
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // Enabling Dynamic soundbar mode will trigger another address allocation.
        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                HdmiControlManager.SOUNDBAR_MODE_ENABLED);
        mTestLooper.dispatchAll();
        verify(mHdmiControlServiceSpy, times(1))
                .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The first allocation finished. The second allocation is still in progress.
        // The audio system is not present in the network.
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
        Mockito.clearInvocations(mHdmiControlServiceSpy);

        mTestLooper.moveTimeForward(allocationDelay / 2);
        mTestLooper.dispatchAll();
        // The second allocation finished. The audio system is present in the network.
        verify(mHdmiControlServiceSpy, times(1))
                .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
        assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
    }

    @Test
    public void failedAddressAllocation_noLocalDevice() {
        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
        mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_3, SendMessageResult.SUCCESS);
        mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS);
        mTestLooper.dispatchAll();

        mHdmiControlServiceSpy.clearCecLocalDevices();
        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
        mTestLooper.dispatchAll();

        assertThat(mHdmiControlServiceSpy.playback()).isNull();
        assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
    }

    @Test
    @Test
    public void earcIdle_blocksArcConnection() {
    public void earcIdle_blocksArcConnection() {
        mHdmiControlServiceSpy.clearEarcLocalDevice();
        mHdmiControlServiceSpy.clearEarcLocalDevice();