summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Piotr WilczyƄski <wilczynskip@google.com> 2024-11-22 16:10:35 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-22 16:10:35 +0000
commit2d8946ad99326382fcb0944dc9c34081be016104 (patch)
tree7c01fee83b2ea73d442baea9aecff7877e095d9b
parent640f25eb172853cc23bc8f12498cc0c7b3d5c1de (diff)
parent2ddac774862d4a77355463681478e9cd5bdce947 (diff)
Merge "Topology listener API" into main
-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 1e66beea42a6..1eec65619d4e 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 {
@@ -1516,6 +1612,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 3871f2a57f76..c74943c95a6a 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;
}
@@ -3502,6 +3503,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);
}
@@ -4137,7 +4160,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:"
@@ -4191,7 +4214,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:
@@ -4252,6 +4275,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 47e96d378149..3843404132c1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -234,6 +234,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();
@@ -3699,8 +3700,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();
@@ -3718,8 +3718,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();
@@ -3733,8 +3732,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();
@@ -3748,8 +3746,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();
@@ -3762,8 +3759,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();
@@ -3774,6 +3770,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
@@ -4225,6 +4264,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