summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Piotr Wilczyński <wilczynskip@google.com> 2024-11-25 16:51:44 +0000
committer Piotr Wilczyński <wilczynskip@google.com> 2024-11-28 17:06:31 +0000
commit6ccbf099e7d33dbca5f94cbaf90574c021c4f4e7 (patch)
tree22b117335b556e5ddfb5891d8c10d2aef47aa82a
parent5518e7f2952a31f5387941fca414c7b204b8a670 (diff)
Topology listener API
Register and unregister topology listener APIs, similar to display listener APIs. Extend the current Display Manager callback to also send topology updates. A topology copy is sent in a topology update and it is sent without holding any locks. Bug: 365075972 Change-Id: I78248f674b29f354d16b7d326be0bd8c19e167a8 Test: DisplayManagerGlobalTest, DisplayManagerServiceTest, DisplayTopologyCoordinatorTest, DisplayTopologyTest Flag: com.android.server.display.feature.flags.display_topology
-rw-r--r--core/java/android/hardware/display/DisplayManager.java26
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java125
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java19
-rw-r--r--core/java/android/hardware/display/IDisplayManagerCallback.aidl3
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java98
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt97
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java68
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java48
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java65
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt20
10 files changed, 478 insertions, 91 deletions
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 25327a9b1d52..7054c37cbc3b 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -24,6 +24,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -70,6 +71,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -1841,6 +1843,30 @@ public final class DisplayManager {
}
/**
+ * Register a listener to receive display topology updates.
+ * @param executor The executor specifying the thread on which the callbacks will be invoked
+ * @param listener The listener
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void registerTopologyListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<DisplayTopology> listener) {
+ mGlobal.registerTopologyListener(executor, listener, ActivityThread.currentPackageName());
+ }
+
+ /**
+ * Unregister a display topology listener.
+ * @param listener The listener to unregister
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void unregisterTopologyListener(@NonNull Consumer<DisplayTopology> listener) {
+ mGlobal.unregisterTopologyListener(listener);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a31b87f5def2..ffa546067eff 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -73,6 +74,7 @@ import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
/**
* Manager communication with the display manager service on behalf of
@@ -126,7 +128,7 @@ public final class DisplayManagerGlobal {
public static final int EVENT_DISPLAY_REFRESH_RATE_CHANGED = 8;
public static final int EVENT_DISPLAY_STATE_CHANGED = 9;
- @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = {
+ @LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = {
INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
@@ -134,7 +136,8 @@ public final class DisplayManagerGlobal {
INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
- INTERNAL_EVENT_FLAG_DISPLAY_STATE
+ INTERNAL_EVENT_FLAG_DISPLAY_STATE,
+ INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InternalEventFlag {}
@@ -147,6 +150,7 @@ public final class DisplayManagerGlobal {
public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 6;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_STATE = 1L << 7;
+ public static final long INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED = 1L << 8;
@UnsupportedAppUsage
private static DisplayManagerGlobal sInstance;
@@ -164,6 +168,9 @@ public final class DisplayManagerGlobal {
private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners =
new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<DisplayTopologyListenerDelegate> mTopologyListeners =
+ new CopyOnWriteArrayList<>();
+
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
private final ColorSpace mWideColorSpace;
private final OverlayProperties mOverlayProperties;
@@ -457,6 +464,18 @@ public final class DisplayManagerGlobal {
}
}
+ private void maybeLogAllTopologyListeners() {
+ if (!extraLogging()) {
+ return;
+ }
+ Slog.i(TAG, "Currently registered display topology listeners:");
+ int i = 0;
+ for (DisplayTopologyListenerDelegate d : mTopologyListeners) {
+ Slog.i(TAG, i + ": " + d);
+ i++;
+ }
+ }
+
/**
* Called when there is a display-related window configuration change. Reroutes the event from
* WindowManager to make sure the {@link Display} fields are up-to-date in the last callback.
@@ -502,9 +521,22 @@ public final class DisplayManagerGlobal {
| INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
| INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
+ if (!mTopologyListeners.isEmpty()) {
+ mask |= INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED;
+ }
return mask;
}
+ private DisplayTopologyListenerDelegate findTopologyListenerLocked(
+ @NonNull Consumer<DisplayTopology> listener) {
+ for (DisplayTopologyListenerDelegate delegate : mTopologyListeners) {
+ if (delegate.mListener == listener) {
+ return delegate;
+ }
+ }
+ return null;
+ }
+
private void registerCallbackIfNeededLocked() {
if (mCallback == null) {
mCallback = new DisplayManagerCallback();
@@ -1316,6 +1348,9 @@ public final class DisplayManagerGlobal {
*/
@RequiresPermission(MANAGE_DISPLAYS)
public void setDisplayTopology(DisplayTopology topology) {
+ if (topology == null) {
+ throw new IllegalArgumentException("Topology must not be null");
+ }
try {
mDm.setDisplayTopology(topology);
} catch (RemoteException ex) {
@@ -1323,6 +1358,57 @@ public final class DisplayManagerGlobal {
}
}
+ /**
+ * @see DisplayManager#registerTopologyListener
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void registerTopologyListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<DisplayTopology> listener, String packageName) {
+ if (!Flags.displayTopology()) {
+ return;
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (extraLogging()) {
+ Slog.i(TAG, "Registering display topology listener: packageName=" + packageName);
+ }
+ synchronized (mLock) {
+ DisplayTopologyListenerDelegate delegate = findTopologyListenerLocked(listener);
+ if (delegate == null) {
+ mTopologyListeners.add(new DisplayTopologyListenerDelegate(listener, executor,
+ packageName));
+ registerCallbackIfNeededLocked();
+ updateCallbackIfNeededLocked();
+ }
+ maybeLogAllTopologyListeners();
+ }
+ }
+
+ /**
+ * @see DisplayManager#unregisterTopologyListener
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void unregisterTopologyListener(@NonNull Consumer<DisplayTopology> listener) {
+ if (!Flags.displayTopology()) {
+ return;
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (extraLogging()) {
+ Slog.i(TAG, "Unregistering display topology listener: " + listener);
+ }
+ synchronized (mLock) {
+ DisplayTopologyListenerDelegate delegate = findTopologyListenerLocked(listener);
+ if (delegate != null) {
+ mTopologyListeners.remove(delegate);
+ updateCallbackIfNeededLocked();
+ }
+ }
+ maybeLogAllTopologyListeners();
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
@@ -1332,6 +1418,16 @@ public final class DisplayManagerGlobal {
}
handleDisplayEvent(displayId, event, false /* forceUpdate */);
}
+
+ @Override
+ public void onTopologyChanged(DisplayTopology topology) {
+ if (DEBUG) {
+ Log.d(TAG, "onTopologyChanged: " + topology);
+ }
+ for (DisplayTopologyListenerDelegate listener : mTopologyListeners) {
+ listener.onTopologyChanged(topology);
+ }
+ }
}
private static final class DisplayListenerDelegate {
@@ -1509,6 +1605,31 @@ public final class DisplayManagerGlobal {
}
}
+ private static final class DisplayTopologyListenerDelegate {
+ private final Consumer<DisplayTopology> mListener;
+ private final Executor mExecutor;
+ private final String mPackageName;
+
+ DisplayTopologyListenerDelegate(@NonNull Consumer<DisplayTopology> listener,
+ @NonNull @CallbackExecutor Executor executor, String packageName) {
+ mExecutor = executor;
+ mListener = listener;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayTopologyListener {packageName=" + mPackageName + "}";
+ }
+
+ void onTopologyChanged(DisplayTopology topology) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending topology update: " + topology);
+ }
+ mExecutor.execute(() -> mListener.accept(topology));
+ }
+ }
+
/**
* The API portion of the key that identifies the unique PropertyInvalidatedCache token which
* changes every time we update the system's display configuration.
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index f00c3a53ad0c..0e53d873e43c 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -283,6 +283,14 @@ public final class DisplayTopology implements Parcelable {
normalize();
}
+ /**
+ * @return A deep copy of the topology that will not be modified by the system.
+ */
+ public DisplayTopology copy() {
+ TreeNode rootCopy = mRoot == null ? null : mRoot.copy();
+ return new DisplayTopology(rootCopy, mPrimaryDisplayId);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -694,6 +702,17 @@ public final class DisplayTopology implements Parcelable {
return Collections.unmodifiableList(mChildren);
}
+ /**
+ * @return A deep copy of the node that will not be modified by the system.
+ */
+ public TreeNode copy() {
+ TreeNode copy = new TreeNode(mDisplayId, mWidth, mHeight, mPosition, mOffset);
+ for (TreeNode child : mChildren) {
+ copy.mChildren.add(child.copy());
+ }
+ return copy;
+ }
+
@Override
public String toString() {
return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
index c50e3fb26156..d05a1b8400b0 100644
--- a/core/java/android/hardware/display/IDisplayManagerCallback.aidl
+++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
@@ -16,7 +16,10 @@
package android.hardware.display;
+import android.hardware.display.DisplayTopology;
+
/** @hide */
interface IDisplayManagerCallback {
oneway void onDisplayEvent(int displayId, int event);
+ oneway void onTopologyChanged(in DisplayTopology topology);
}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 6a5224d4524b..7a5b3064b3a3 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -55,6 +55,9 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
/**
* Tests for {@link DisplayManagerGlobal}.
*
@@ -79,10 +82,13 @@ public class DisplayManagerGlobalTest {
private IDisplayManager mDisplayManager;
@Mock
- private DisplayManager.DisplayListener mListener;
+ private DisplayManager.DisplayListener mDisplayListener;
+
+ @Mock
+ private DisplayManager.DisplayListener mDisplayListener2;
@Mock
- private DisplayManager.DisplayListener mListener2;
+ private Consumer<DisplayTopology> mTopologyListener;
@Captor
private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor;
@@ -90,6 +96,7 @@ public class DisplayManagerGlobalTest {
private Context mContext;
private DisplayManagerGlobal mDisplayManagerGlobal;
private Handler mHandler;
+ private Executor mExecutor;
@Before
public void setUp() throws RemoteException {
@@ -97,13 +104,14 @@ public class DisplayManagerGlobalTest {
Mockito.when(mDisplayManager.getPreferredWideGamutColorSpaceId()).thenReturn(0);
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mHandler = mContext.getMainThreadHandler();
+ mExecutor = mContext.getMainExecutor();
mDisplayManagerGlobal = new DisplayManagerGlobal(mDisplayManager);
}
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
- null);
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -111,31 +119,31 @@ public class DisplayManagerGlobalTest {
int displayId = 1;
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
- Mockito.verify(mListener).onDisplayAdded(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayAdded(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
// Mock IDisplayManager to return a different display info to trigger display change.
final DisplayInfo newDisplayInfo = new DisplayInfo();
newDisplayInfo.rotation++;
doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
- Mockito.verify(mListener).onDisplayChanged(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
- Mockito.verify(mListener).onDisplayRemoved(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayRemoved(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
public void testDisplayListenerIsCalled_WhenDisplayPropertyChangeEventOccurs()
throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| INTERNAL_EVENT_FLAG_DISPLAY_STATE,
null);
@@ -145,50 +153,50 @@ public class DisplayManagerGlobalTest {
int displayId = 1;
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED);
waitForHandler();
- Mockito.verify(mListener).onDisplayChanged(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
- Mockito.reset(mListener);
+ Mockito.reset(mDisplayListener);
callback.onDisplayEvent(displayId, EVENT_DISPLAY_STATE_CHANGED);
waitForHandler();
- Mockito.verify(mListener).onDisplayChanged(eq(displayId));
- Mockito.verifyNoMoreInteractions(mListener);
+ Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+ Mockito.verifyNoMoreInteractions(mDisplayListener);
}
@Test
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
- null);
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+ ALL_DISPLAY_EVENTS, /* packageName= */ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
int displayId = 1;
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
& ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
- Mockito.verifyZeroInteractions(mListener);
+ Mockito.verifyZeroInteractions(mDisplayListener);
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
& ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
- Mockito.verifyZeroInteractions(mListener);
+ Mockito.verifyZeroInteractions(mDisplayListener);
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
& ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
- Mockito.verifyZeroInteractions(mListener);
+ Mockito.verifyZeroInteractions(mDisplayListener);
}
@Test
@@ -207,7 +215,7 @@ public class DisplayManagerGlobalTest {
@Test
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
null);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
@@ -228,7 +236,7 @@ public class DisplayManagerGlobalTest {
.registerCallbackWithEventMask(mCallbackCaptor.capture(),
eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
- mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+ mDisplayManagerGlobal.unregisterDisplayListener(mDisplayListener);
inOrder.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
}
@@ -244,33 +252,49 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(123);
// One listener listens on add/remove, and the other one listens on change.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
null /* packageName */);
- mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler,
+ mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
null /* packageName */);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
- verify(mListener, never()).onDisplayChanged(anyInt());
- verify(mListener2).onDisplayChanged(321);
+ verify(mDisplayListener, never()).onDisplayChanged(anyInt());
+ verify(mDisplayListener2).onDisplayChanged(321);
// Trigger the callback again even if the display info is not changed.
- clearInvocations(mListener2);
+ clearInvocations(mDisplayListener2);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
- verify(mListener2).onDisplayChanged(321);
+ verify(mDisplayListener2).onDisplayChanged(321);
// No callback for non-existing display (no display info returned from IDisplayManager).
- clearInvocations(mListener2);
+ clearInvocations(mDisplayListener2);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(456);
waitForHandler();
- verify(mListener2, never()).onDisplayChanged(anyInt());
+ verify(mDisplayListener2, never()).onDisplayChanged(anyInt());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_TOPOLOGY)
+ public void testTopologyListenerIsCalled_WhenTopologyUpdateOccurs() throws RemoteException {
+ mDisplayManagerGlobal.registerTopologyListener(mExecutor, mTopologyListener,
+ /* packageName= */ null);
+ Mockito.verify(mDisplayManager).registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED));
+ IDisplayManagerCallback callback = mCallbackCaptor.getValue();
+
+ DisplayTopology topology = new DisplayTopology();
+ callback.onTopologyChanged(topology);
+ waitForHandler();
+ Mockito.verify(mTopologyListener).accept(topology);
+ Mockito.verifyNoMoreInteractions(mTopologyListener);
}
@Test
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index 8969b2b72e77..f584ab971d04 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -513,7 +513,7 @@ class DisplayTopologyTest {
val nodes = rearrangeRects(
RectF(0f, 0f, 150f, 100f),
RectF(-150f, 0f, 0f, 100f),
- RectF(0f,-100f, 150f, 0f),
+ RectF(0f, -100f, 150f, 0f),
RectF(150f, 0f, 300f, 100f),
RectF(0f, 100f, 150f, 200f),
)
@@ -584,15 +584,15 @@ class DisplayTopologyTest {
@Test
fun rearrange_useLargerEdge() {
val nodes = rearrangeRects(
- //444111
- //444111
- //444111
- // 000222
- // 000222
- // 000222
- // 333
- // 333
- // 333
+ // 444111
+ // 444111
+ // 444111
+ // 000222
+ // 000222
+ // 000222
+ // 333
+ // 333
+ // 333
RectF(20f, 30f, 50f, 60f),
RectF(30f, 0f, 60f, 30f),
RectF(50f, 30f, 80f, 60f),
@@ -617,24 +617,25 @@ class DisplayTopologyTest {
@Test
fun rearrange_closeGaps() {
val nodes = rearrangeRects(
- //000
- //000 111
- //000 111
- // 111
+ // 000
+ // 000 111
+ // 000 111
+ // 111
//
- // 222
- // 222
- // 222
+ // 222
+ // 222
+ // 222
RectF(0f, 0f, 30f, 30f),
RectF(40f, 10f, 70f, 40f),
- RectF(80.5f, 50f, 110f, 80f), // left+=0.5 to cause a preference for TOP/BOTTOM attach
+ RectF(80.5f, 50f, 110f, 80f), // left+=0.5 to cause a preference for
+ // TOP/BOTTOM attach
)
assertPositioning(
nodes,
// In the case of corner adjacency, we prefer a left/right attachment.
Pair(POSITION_RIGHT, 10f),
- Pair(POSITION_BOTTOM, 40.5f), // TODO: fix implementation to remove this gap
+ Pair(POSITION_BOTTOM, 40.5f), // TODO: fix implementation to remove this gap
)
assertThat(nodes[0].children).containsExactly(nodes[1])
@@ -642,11 +643,65 @@ class DisplayTopologyTest {
assertThat(nodes[2].children).isEmpty()
}
+ @Test
+ fun copy() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+ display1.addChild(display3)
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.addChild(display4)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ val copy = topology.copy()
+
+ assertThat(copy.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = copy.root!!
+ assertThat(actualDisplay1.displayId).isEqualTo(1)
+ assertThat(actualDisplay1.width).isEqualTo(200f)
+ assertThat(actualDisplay1.height).isEqualTo(600f)
+ assertThat(actualDisplay1.children).hasSize(2)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ assertThat(actualDisplay2.displayId).isEqualTo(2)
+ assertThat(actualDisplay2.width).isEqualTo(600f)
+ assertThat(actualDisplay2.height).isEqualTo(200f)
+ assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.offset).isEqualTo(0f)
+ assertThat(actualDisplay2.children).hasSize(1)
+
+ val actualDisplay3 = actualDisplay1.children[1]
+ assertThat(actualDisplay3.displayId).isEqualTo(3)
+ assertThat(actualDisplay3.width).isEqualTo(600f)
+ assertThat(actualDisplay3.height).isEqualTo(200f)
+ assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.offset).isEqualTo(400f)
+ assertThat(actualDisplay3.children).isEmpty()
+
+ val actualDisplay4 = actualDisplay2.children[0]
+ assertThat(actualDisplay4.displayId).isEqualTo(4)
+ assertThat(actualDisplay4.width).isEqualTo(200f)
+ assertThat(actualDisplay4.height).isEqualTo(600f)
+ assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.offset).isEqualTo(0f)
+ assertThat(actualDisplay4.children).isEmpty()
+ }
+
/**
* Runs the rearrange algorithm and returns the resulting tree as a list of nodes, with the
* root at index 0. The number of nodes is inferred from the number of positions passed.
*/
- private fun rearrangeRects(vararg pos : RectF) : List<DisplayTopology.TreeNode> {
+ private fun rearrangeRects(vararg pos: RectF): List<DisplayTopology.TreeNode> {
// Generates a linear sequence of nodes in order in the List from root to leaf,
// left-to-right. IDs are ascending from 0 to count - 1.
@@ -668,7 +723,7 @@ class DisplayTopologyTest {
}
private fun assertPositioning(
- nodes : List<DisplayTopology.TreeNode>, vararg positions : Pair<Int, Float>) {
+ nodes: List<DisplayTopology.TreeNode>, vararg positions: Pair<Int, Float>) {
assertThat(nodes.drop(1).map { Pair(it.position, it.offset )})
.containsExactly(*positions)
.inOrder()
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0e5fc41f3c32..5c6299559856 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -668,7 +668,8 @@ public final class DisplayManagerService extends SystemService {
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
if (mFlags.isDisplayTopologyEnabled()) {
mDisplayTopologyCoordinator =
- new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled);
+ new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled,
+ this::deliverTopologyUpdate, new HandlerExecutor(mHandler), mSyncRoot);
} else {
mDisplayTopologyCoordinator = null;
}
@@ -3507,6 +3508,28 @@ public final class DisplayManagerService extends SystemService {
callbackRecord.notifyDisplayEventAsync(displayId, event);
}
+ private void deliverTopologyUpdate(DisplayTopology topology) {
+ if (DEBUG) {
+ Slog.d(TAG, "Delivering topology update");
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
+ }
+
+ // Grab the lock and copy the callbacks.
+ List<CallbackRecord> callbacks = new ArrayList<>();
+ synchronized (mSyncRoot) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ callbacks.add(mCallbacks.valueAt(i));
+ }
+ }
+
+ // After releasing the lock, send the notifications out.
+ for (CallbackRecord callback : callbacks) {
+ callback.notifyTopologyUpdateAsync(topology);
+ }
+ }
+
private boolean extraLogging(String packageName) {
return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
}
@@ -4142,7 +4165,7 @@ public final class DisplayManagerService extends SystemService {
* cached or frozen.
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
- if (!shouldSendEvent(event)) {
+ if (!shouldSendDisplayEvent(event)) {
if (extraLogging(mPackageName)) {
Slog.i(TAG,
"Not sending displayEvent: " + event + " due to mask:"
@@ -4196,7 +4219,7 @@ public final class DisplayManagerService extends SystemService {
/**
* Return true if the client is interested in this event.
*/
- private boolean shouldSendEvent(@DisplayEvent int event) {
+ private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
final long mask = mInternalEventFlagsMask.get();
switch (event) {
case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
@@ -4257,6 +4280,45 @@ public final class DisplayManagerService extends SystemService {
mPendingEvents.add(new Event(displayId, event));
}
+ /**
+ * @return {@code false} if RemoteException happens; otherwise {@code true} for
+ * success.
+ */
+ boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
+ if ((mInternalEventFlagsMask.get()
+ & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED) == 0) {
+ if (extraLogging(mPackageName)) {
+ Slog.i(TAG, "Not sending topology update: " + topology + " due to mask: "
+ + mInternalEventFlagsMask);
+ }
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "notifyTopologyUpdateAsync#notSendingUpdate=" + topology
+ + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
+ }
+ // The client is not interested in this event, so do nothing.
+ return true;
+ }
+ return transmitTopologyUpdate(topology);
+ }
+
+ /**
+ * Transmit a single display topology update. The client is presumed ready. Return true on
+ * success and false if the client died.
+ */
+ private boolean transmitTopologyUpdate(DisplayTopology topology) {
+ // The client is ready to receive the event.
+ try {
+ mCallback.onTopologyChanged(topology);
+ return true;
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that display topology changed, assuming it died.", ex);
+ binderDied();
+ return false;
+ }
+ }
+
// Send all pending events. This can safely be called if the process is not ready, but it
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 47226861545f..55b292aefec4 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -25,7 +25,9 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
/**
* Manages the relative placement (topology) of extended displays. Responsible for updating and
@@ -33,7 +35,7 @@ import java.util.function.BooleanSupplier;
*/
class DisplayTopologyCoordinator {
- @GuardedBy("mLock")
+ @GuardedBy("mSyncRoot")
private DisplayTopology mTopology;
/**
@@ -41,16 +43,31 @@ class DisplayTopologyCoordinator {
*/
private final BooleanSupplier mIsExtendedDisplayEnabled;
- private final Object mLock = new Object();
-
- DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled) {
- this(new Injector(), isExtendedDisplayEnabled);
+ /**
+ * Callback used to send topology updates.
+ * Should be invoked from the corresponding executor.
+ * A copy of the topology should be sent that will not be modified by the system.
+ */
+ private final Consumer<DisplayTopology> mOnTopologyChangedCallback;
+ private final Executor mTopologyChangeExecutor;
+ private final DisplayManagerService.SyncRoot mSyncRoot;
+
+ DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
+ Consumer<DisplayTopology> onTopologyChangedCallback,
+ Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
+ this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
+ topologyChangeExecutor, syncRoot);
}
@VisibleForTesting
- DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled) {
+ DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
+ Consumer<DisplayTopology> onTopologyChangedCallback,
+ Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
mTopology = injector.getTopology();
mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
+ mOnTopologyChangedCallback = onTopologyChangedCallback;
+ mTopologyChangeExecutor = topologyChangeExecutor;
+ mSyncRoot = syncRoot;
}
/**
@@ -61,8 +78,9 @@ class DisplayTopologyCoordinator {
if (!isDisplayAllowedInTopology(info)) {
return;
}
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info));
+ sendTopologyUpdateLocked();
}
}
@@ -71,8 +89,9 @@ class DisplayTopologyCoordinator {
* @param displayId The logical display ID
*/
void onDisplayRemoved(int displayId) {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology.removeDisplay(displayId);
+ sendTopologyUpdateLocked();
}
}
@@ -80,14 +99,15 @@ class DisplayTopologyCoordinator {
* @return A deep copy of the topology.
*/
DisplayTopology getTopology() {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
return mTopology;
}
}
void setTopology(DisplayTopology topology) {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology = topology;
+ sendTopologyUpdateLocked();
}
}
@@ -96,7 +116,7 @@ class DisplayTopologyCoordinator {
* @param pw The stream to dump information to.
*/
void dump(PrintWriter pw) {
- synchronized (mLock) {
+ synchronized (mSyncRoot) {
mTopology.dump(pw);
}
}
@@ -124,6 +144,12 @@ class DisplayTopologyCoordinator {
&& info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
}
+ @GuardedBy("mSyncRoot")
+ private void sendTopologyUpdateLocked() {
+ DisplayTopology copy = mTopology.copy();
+ mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(copy));
+ }
+
@VisibleForTesting
static class Injector {
DisplayTopology getTopology() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 1b56b3ff0654..365cbaed2aac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -233,6 +233,7 @@ public class DisplayManagerServiceTest {
private static final String DISPLAY_GROUP_EVENT_ADDED = "DISPLAY_GROUP_EVENT_ADDED";
private static final String DISPLAY_GROUP_EVENT_REMOVED = "DISPLAY_GROUP_EVENT_REMOVED";
private static final String DISPLAY_GROUP_EVENT_CHANGED = "DISPLAY_GROUP_EVENT_CHANGED";
+ private static final String TOPOLOGY_CHANGED_EVENT = "TOPOLOGY_CHANGED_EVENT";
@Rule(order = 0)
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -3716,8 +3717,7 @@ public class DisplayManagerServiceTest {
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
manageDisplaysPermission(/* granted= */ true);
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3735,8 +3735,7 @@ public class DisplayManagerServiceTest {
public void testGetDisplayTopology_NullIfFlagDisabled() {
manageDisplaysPermission(/* granted= */ true);
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3750,8 +3749,7 @@ public class DisplayManagerServiceTest {
@Test
public void testGetDisplayTopology_withoutPermission_shouldThrowException() {
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3765,8 +3763,7 @@ public class DisplayManagerServiceTest {
public void testSetDisplayTopology() {
manageDisplaysPermission(/* granted= */ true);
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3779,8 +3776,7 @@ public class DisplayManagerServiceTest {
@Test
public void testSetDisplayTopology_withoutPermission_shouldThrowException() {
when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
@@ -3791,6 +3787,49 @@ public class DisplayManagerServiceTest {
() -> displayManagerBinderService.setDisplayTopology(new DisplayTopology()));
}
+ @Test
+ public void testShouldNotifyTopologyChanged() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(callback,
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
+ waitForIdleHandler(handler);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).containsExactly(TOPOLOGY_CHANGED_EVENT);
+ }
+
+ @Test
+ public void testShouldNotNotifyTopologyChanged_WhenClientIsNotSubscribed() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ // Only subscribe to display events, not topology events
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(callback,
+ STANDARD_DISPLAY_EVENTS);
+ waitForIdleHandler(handler);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).isEmpty();
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -4242,6 +4281,12 @@ public class DisplayManagerServiceTest {
eventSeen(DISPLAY_GROUP_EVENT_CHANGED);
}
+ @Override
+ public void onTopologyChanged(DisplayTopology topology) {
+ mReceivedEvents.add(TOPOLOGY_CHANGED_EVENT);
+ eventSeen(TOPOLOGY_CHANGED_EVENT);
+ }
+
public void clear() {
mReceivedEvents.clear();
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index a2d2a81b20b4..e4b461f307d3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -24,18 +24,21 @@ import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import java.util.function.BooleanSupplier
class DisplayTopologyCoordinatorTest {
private lateinit var coordinator: DisplayTopologyCoordinator
private val displayInfo = DisplayInfo()
+ private val topologyChangeExecutor = Runnable::run
private val mockTopology = mock<DisplayTopology>()
- private val mockIsExtendedDisplayEnabled = mock<BooleanSupplier>()
+ private val mockTopologyCopy = mock<DisplayTopology>()
+ private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
+ private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>()
@Before
fun setUp() {
@@ -47,13 +50,14 @@ class DisplayTopologyCoordinatorTest {
val injector = object : DisplayTopologyCoordinator.Injector() {
override fun getTopology() = mockTopology
}
- coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled)
+ whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
+ whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
+ coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
+ mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot())
}
@Test
fun addDisplay() {
- whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(true)
-
coordinator.onDisplayAdded(displayInfo)
val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
@@ -61,24 +65,26 @@ class DisplayTopologyCoordinatorTest {
val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
/ displayInfo.logicalDensityDpi)
verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
+ verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
}
@Test
fun addDisplay_extendedDisplaysDisabled() {
- whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(false)
+ whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
coordinator.onDisplayAdded(displayInfo)
verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+ verify(mockTopologyChangedCallback, never()).invoke(any())
}
@Test
fun addDisplay_notInDefaultDisplayGroup() {
- whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(true)
displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
coordinator.onDisplayAdded(displayInfo)
verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+ verify(mockTopologyChangedCallback, never()).invoke(any())
}
} \ No newline at end of file