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

Commit 14baad01 authored by Qijing Yao's avatar Qijing Yao
Browse files

Send topology to newly registered listener

This commit adds a mechanism to send the current `DisplayTopology` to
newly registered `OnDisplaysChangedListener` instances.

When a listener is added via `addDisplayWindowListener`, it now
receives a call to `onTopologyChanged` with the current
`DisplayTopology`. This ensures that listeners are immediately aware
of the display topology upon registration, without having to wait for
a topology change event.

Bug: 383069173
Test: manual and atest
Flag: com.android.window.flags.enable_connected_displays_window_drag
Change-Id: I3c578de2786c17257cf6cec0e30efdf7832efa5f
parent e018f198
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ public class DisplayController {
    private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
    private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
    private final Map<Integer, RectF> mUnpopulatedDisplayBounds = new HashMap<>();
    private DisplayTopology mDisplayTopology;

    public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
            ShellExecutor mainExecutor, DisplayManager displayManager) {
@@ -157,6 +158,7 @@ public class DisplayController {
            for (int i = 0; i < mDisplays.size(); ++i) {
                listener.onDisplayAdded(mDisplays.keyAt(i));
            }
            listener.onTopologyChanged(mDisplayTopology);
        }
    }

@@ -245,6 +247,7 @@ public class DisplayController {
        if (topology == null) {
            return;
        }
        mDisplayTopology = topology;
        SparseArray<RectF> absoluteBounds = topology.getAbsoluteBounds();
        mUnpopulatedDisplayBounds.clear();
        for (int i = 0; i < absoluteBounds.size(); ++i) {
+176 −10
Original line number Diff line number Diff line
@@ -16,26 +16,52 @@

package com.android.wm.shell.common;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayTopology;
import android.os.RemoteException;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableContext;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestSyncExecutor;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;

import java.util.function.Consumer;

/**
 * Tests for the display controller.
@@ -46,23 +72,163 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayControllerTests extends ShellTestCase {

    private @Mock Context mContext;
    private @Mock IWindowManager mWM;
    private @Mock ShellInit mShellInit;
    private @Mock ShellExecutor mMainExecutor;
    private @Mock DisplayManager mDisplayManager;
    @Mock private IWindowManager mWM;
    @Mock private ShellInit mShellInit;
    @Mock private DisplayManager mDisplayManager;
    @Mock private DisplayTopology mMockTopology;
    @Mock private DisplayController.OnDisplaysChangedListener mListener;
    private StaticMockitoSession mMockitoSession;
    private TestSyncExecutor mMainExecutor;
    private IDisplayWindowListener mDisplayContainerListener;
    private Consumer<DisplayTopology> mCapturedTopologyListener;
    private Display mMockDisplay;
    private DisplayController mController;
    private static final int DISPLAY_ID_0 = 0;
    private static final int DISPLAY_ID_1 = 1;
    private static final RectF DISPLAY_ABS_BOUNDS_0 = new RectF(10, 10, 20, 20);
    private static final RectF DISPLAY_ABS_BOUNDS_1 = new RectF(11, 11, 22, 22);

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    public void setUp() throws RemoteException {
        mMockitoSession =
                ExtendedMockito.mockitoSession()
                        .initMocks(this)
                        .mockStatic(DesktopModeStatus.class)
                        .strictness(Strictness.LENIENT)
                        .startMocking();

        mContext = spy(new TestableContext(
                androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
                        .getContext(), null));

        mMainExecutor = new TestSyncExecutor();
        mController = new DisplayController(
                mContext, mWM, mShellInit, mMainExecutor, mDisplayManager);

        mMockDisplay = mock(Display.class);
        when(mMockDisplay.getDisplayAdjustments()).thenReturn(
                new DisplayAdjustments(new Configuration()));
        when(mDisplayManager.getDisplay(anyInt())).thenReturn(mMockDisplay);
        when(mDisplayManager.getDisplayTopology()).thenReturn(mMockTopology);
        doAnswer(invocation -> {
            mDisplayContainerListener = invocation.getArgument(0);
            return new int[]{DISPLAY_ID_0};
        }).when(mWM).registerDisplayWindowListener(any());
        doAnswer(invocation -> {
            mCapturedTopologyListener = invocation.getArgument(1);
            return null;
        }).when(mDisplayManager).registerTopologyListener(any(), any());
        SparseArray<RectF> absoluteBounds = new SparseArray<>();
        absoluteBounds.put(DISPLAY_ID_0, DISPLAY_ABS_BOUNDS_0);
        absoluteBounds.put(DISPLAY_ID_1, DISPLAY_ABS_BOUNDS_1);
        when(mMockTopology.getAbsoluteBounds()).thenReturn(absoluteBounds);
    }

    @After
    public void tearDown() {
        if (mMockitoSession != null) {
            mMockitoSession.finishMocking();
        }
    }

    @Test
    public void instantiateController_addInitCallback() {
        verify(mShellInit, times(1)).addInitCallback(any(), eq(mController));
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    public void onInit_canEnterDesktopMode_registerListeners() throws RemoteException {
        ExtendedMockito.doReturn(true)
                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));

        mController.onInit();

        assertNotNull(mController.getDisplayContext(DISPLAY_ID_0));
        verify(mWM).registerDisplayWindowListener(any());
        verify(mDisplayManager).registerTopologyListener(eq(mMainExecutor), any());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    public void onInit_canNotEnterDesktopMode_onlyRegisterDisplayWindowListener()
            throws RemoteException {
        ExtendedMockito.doReturn(false)
                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));

        mController.onInit();

        assertNotNull(mController.getDisplayContext(DISPLAY_ID_0));
        verify(mWM).registerDisplayWindowListener(any());
        verify(mDisplayManager, never()).registerTopologyListener(any(), any());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    public void addDisplayWindowListener_notifiesExistingDisplaysAndTopology() {
        ExtendedMockito.doReturn(true)
                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));

        mController.onInit();
        mController.addDisplayWindowListener(mListener);

        verify(mListener).onDisplayAdded(eq(DISPLAY_ID_0));
        verify(mListener).onTopologyChanged(eq(mMockTopology));
    }

    @Test
    public void onDisplayAddedAndRemoved_updatesDisplayContexts() throws RemoteException {
        mController.onInit();
        mController.addDisplayWindowListener(mListener);

        mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);

        verify(mListener).onDisplayAdded(eq(DISPLAY_ID_0));
        verify(mListener).onDisplayAdded(eq(DISPLAY_ID_1));
        assertNotNull(mController.getDisplayContext(DISPLAY_ID_1));
        verify(mContext).createDisplayContext(eq(mMockDisplay));

        mDisplayContainerListener.onDisplayRemoved(DISPLAY_ID_1);

        assertNull(mController.getDisplayContext(DISPLAY_ID_1));
        verify(mListener).onDisplayRemoved(eq(DISPLAY_ID_1));
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    public void onDisplayTopologyChanged_updateDisplayLayout() throws RemoteException {
        ExtendedMockito.doReturn(true)
                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
        mController.onInit();
        mController.addDisplayWindowListener(mListener);
        mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);

        mCapturedTopologyListener.accept(mMockTopology);

        assertEquals(DISPLAY_ABS_BOUNDS_0, mController.getDisplayLayout(DISPLAY_ID_0)
                .globalBoundsDp());
        assertEquals(DISPLAY_ABS_BOUNDS_1, mController.getDisplayLayout(DISPLAY_ID_1)
                .globalBoundsDp());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
    public void onDisplayTopologyChanged_topologyBeforeDisplayAdded_appliesBoundsOnAdd()
            throws RemoteException {
        ExtendedMockito.doReturn(true)
                .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
        mController.onInit();
        mController.addDisplayWindowListener(mListener);

        mCapturedTopologyListener.accept(mMockTopology);

        assertNull(mController.getDisplayLayout(DISPLAY_ID_1));

        mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);

        assertEquals(DISPLAY_ABS_BOUNDS_0,
                mController.getDisplayLayout(DISPLAY_ID_0).globalBoundsDp());
        assertEquals(DISPLAY_ABS_BOUNDS_1,
                mController.getDisplayLayout(DISPLAY_ID_1).globalBoundsDp());
    }
}