Loading core/java/android/hardware/display/DisplayManager.java +26 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -1840,6 +1842,30 @@ public final class DisplayManager { mGlobal.setDisplayTopology(topology); } /** * 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. */ Loading core/java/android/hardware/display/DisplayManagerGlobal.java +123 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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, Loading @@ -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 {} Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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(); Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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 { Loading Loading @@ -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. Loading core/java/android/hardware/display/DisplayTopology.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading core/java/android/hardware/display/IDisplayManagerCallback.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -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); } core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +61 −37 Original line number Diff line number Diff line Loading @@ -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}. * Loading @@ -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; Loading @@ -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 { Loading @@ -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(); Loading @@ -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); Loading @@ -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 Loading @@ -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); Loading @@ -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)); } Loading @@ -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 Loading Loading
core/java/android/hardware/display/DisplayManager.java +26 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -1840,6 +1842,30 @@ public final class DisplayManager { mGlobal.setDisplayTopology(topology); } /** * 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. */ Loading
core/java/android/hardware/display/DisplayManagerGlobal.java +123 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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, Loading @@ -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 {} Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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(); Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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 { Loading Loading @@ -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. Loading
core/java/android/hardware/display/DisplayTopology.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading
core/java/android/hardware/display/IDisplayManagerCallback.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -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); }
core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +61 −37 Original line number Diff line number Diff line Loading @@ -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}. * Loading @@ -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; Loading @@ -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 { Loading @@ -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(); Loading @@ -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); Loading @@ -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 Loading @@ -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); Loading @@ -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)); } Loading @@ -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 Loading