From 204eba6cdc4ff465a8f457008e2783c8b5f67c01 Mon Sep 17 00:00:00 2001 From: Marin Shalamanov Date: Tue, 19 Jan 2021 14:13:30 +0100 Subject: [PATCH] Add eventMask to DM.registerDisplayListener Add a new parameter to DisplayManager.registDisplayListener which clients can use to specify in which events they are interested. Test: atest DisplayManagerServiceTest atest DisplayManagerGlobalTest Bug: 171240622 Change-Id: Icc71bdd9ddb390bc5406b298a557c84194a53c00 --- .../hardware/display/DisplayManager.java | 64 ++++- .../display/DisplayManagerGlobal.java | 85 +++++-- .../hardware/display/IDisplayManager.aidl | 1 + .../display/DisplayManagerGlobalTest.java | 127 ++++++++++ .../server/display/DisplayManagerService.java | 61 ++++- .../display/DisplayManagerServiceTest.java | 218 ++++++++++++++---- 6 files changed, 488 insertions(+), 68 deletions(-) create mode 100644 core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 2c3e7f18a3ab4..6dd67447c3213 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.IntDef; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -376,6 +377,43 @@ public final class DisplayManager { @TestApi public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; + /** + * @hide + */ + @LongDef(flag = true, prefix = {"EVENT_FLAG_"}, value = { + EVENT_FLAG_DISPLAY_ADDED, + EVENT_FLAG_DISPLAY_CHANGED, + EVENT_FLAG_DISPLAY_REMOVED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventsMask {} + + /** + * Event type for when a new display is added. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + * + * @hide + */ + public static final long EVENT_FLAG_DISPLAY_ADDED = 1L << 0; + + /** + * Event type for when a display is removed. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + * + * @hide + */ + public static final long EVENT_FLAG_DISPLAY_REMOVED = 1L << 1; + + /** + * Event type for when a display is changed. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + * + * @hide + */ + public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2; /** @hide */ public DisplayManager(Context context) { @@ -486,7 +524,7 @@ private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) { } /** - * Registers an display listener to receive notifications about when + * Registers a display listener to receive notifications about when * displays are added, removed or changed. * * @param listener The listener to register. @@ -496,7 +534,29 @@ private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) { * @see #unregisterDisplayListener */ public void registerDisplayListener(DisplayListener listener, Handler handler) { - mGlobal.registerDisplayListener(listener, handler); + registerDisplayListener(listener, handler, EVENT_FLAG_DISPLAY_ADDED + | EVENT_FLAG_DISPLAY_CHANGED | EVENT_FLAG_DISPLAY_REMOVED); + } + + /** + * Registers a display listener to receive notifications about given display event types. + * + * @param listener The listener to register. + * @param handler The handler on which the listener should be invoked, or null + * if the listener should be invoked on the calling thread's looper. + * @param eventsMask A bitmask of the event types for which this listener is subscribed. + * + * @see #EVENT_FLAG_DISPLAY_ADDED + * @see #EVENT_FLAG_DISPLAY_CHANGED + * @see #EVENT_FLAG_DISPLAY_REMOVED + * @see #registerDisplayListener(DisplayListener, Handler) + * @see #unregisterDisplayListener + * + * @hide + */ + public void registerDisplayListener(@NonNull DisplayListener listener, + @Nullable Handler handler, @EventsMask long eventsMask) { + mGlobal.registerDisplayListener(listener, handler, eventsMask); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 60fe5825d6a1c..fd0431c5bc3fb 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -16,6 +16,9 @@ package android.hardware.display; +import static android.hardware.display.DisplayManager.EventsMask; + +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PropertyInvalidatedCache; @@ -42,6 +45,10 @@ import android.view.DisplayInfo; import android.view.Surface; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -66,6 +73,14 @@ public final class DisplayManagerGlobal { // orientation change before the display info cache has actually been invalidated. private static final boolean USE_CACHE = false; + @IntDef(prefix = {"SWITCHING_TYPE_"}, value = { + EVENT_DISPLAY_ADDED, + EVENT_DISPLAY_CHANGED, + EVENT_DISPLAY_REMOVED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DisplayEvent {} + public static final int EVENT_DISPLAY_ADDED = 1; public static final int EVENT_DISPLAY_CHANGED = 2; public static final int EVENT_DISPLAY_REMOVED = 3; @@ -81,16 +96,17 @@ public final class DisplayManagerGlobal { private final IDisplayManager mDm; private DisplayManagerCallback mCallback; - private final ArrayList mDisplayListeners = - new ArrayList(); + private @EventsMask long mRegisteredEventsMask = 0; + private final ArrayList mDisplayListeners = new ArrayList<>(); - private final SparseArray mDisplayInfoCache = new SparseArray(); + private final SparseArray mDisplayInfoCache = new SparseArray<>(); private final ColorSpace mWideColorSpace; private int[] mDisplayIdCache; private int mWifiDisplayScanNestCount; - private DisplayManagerGlobal(IDisplayManager dm) { + @VisibleForTesting + public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; try { mWideColorSpace = @@ -274,18 +290,25 @@ public Display getRealDisplay(int displayId) { * If that is still null, a runtime exception will be thrown. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @Nullable Handler handler) { + @Nullable Handler handler, @EventsMask long eventsMask) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } + if (eventsMask == 0) { + throw new IllegalArgumentException("The set of events to listen to must not be empty."); + } + synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { Looper looper = getLooperForHandler(handler); - mDisplayListeners.add(new DisplayListenerDelegate(listener, looper)); + mDisplayListeners.add(new DisplayListenerDelegate(listener, looper, eventsMask)); registerCallbackIfNeededLocked(); + } else { + mDisplayListeners.get(index).setEventsMask(eventsMask); } + updateCallbackIfNeededLocked(); } } @@ -300,6 +323,7 @@ public void unregisterDisplayListener(DisplayListener listener) { DisplayListenerDelegate d = mDisplayListeners.get(index); d.clearEvents(); mDisplayListeners.remove(index); + updateCallbackIfNeededLocked(); } } } @@ -325,18 +349,36 @@ private int findDisplayListenerLocked(DisplayListener listener) { return -1; } + @EventsMask + private int calculateEventsMaskLocked() { + int mask = 0; + final int numListeners = mDisplayListeners.size(); + for (int i = 0; i < numListeners; i++) { + mask |= mDisplayListeners.get(i).mEventsMask; + } + return mask; + } + private void registerCallbackIfNeededLocked() { if (mCallback == null) { mCallback = new DisplayManagerCallback(); + updateCallbackIfNeededLocked(); + } + } + + private void updateCallbackIfNeededLocked() { + int mask = calculateEventsMaskLocked(); + if (mask != mRegisteredEventsMask) { try { - mDm.registerCallback(mCallback); + mDm.registerCallbackWithEventMask(mCallback, mask); + mRegisteredEventsMask = mask; } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } } - private void handleDisplayEvent(int displayId, int event) { + private void handleDisplayEvent(int displayId, @DisplayEvent int event) { synchronized (mLock) { if (USE_CACHE) { mDisplayInfoCache.remove(displayId); @@ -754,7 +796,7 @@ public int getRefreshRateSwitchingType() { private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override - public void onDisplayEvent(int displayId, int event) { + public void onDisplayEvent(int displayId, @DisplayEvent int event) { if (DEBUG) { Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); } @@ -764,13 +806,16 @@ public void onDisplayEvent(int displayId, int event) { private static final class DisplayListenerDelegate extends Handler { public final DisplayListener mListener; + public long mEventsMask; - DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper) { + DisplayListenerDelegate(DisplayListener listener, @NonNull Looper looper, + @EventsMask long eventsMask) { super(looper, null, true /*async*/); mListener = listener; + mEventsMask = eventsMask; } - public void sendDisplayEvent(int displayId, int event) { + public void sendDisplayEvent(int displayId, @DisplayEvent int event) { Message msg = obtainMessage(event, displayId, 0); sendMessage(msg); } @@ -779,17 +824,27 @@ public void clearEvents() { removeCallbacksAndMessages(null); } + public synchronized void setEventsMask(@EventsMask long newEventsMask) { + mEventsMask = newEventsMask; + } + @Override - public void handleMessage(Message msg) { + public synchronized void handleMessage(Message msg) { switch (msg.what) { case EVENT_DISPLAY_ADDED: - mListener.onDisplayAdded(msg.arg1); + if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { + mListener.onDisplayAdded(msg.arg1); + } break; case EVENT_DISPLAY_CHANGED: - mListener.onDisplayChanged(msg.arg1); + if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { + mListener.onDisplayChanged(msg.arg1); + } break; case EVENT_DISPLAY_REMOVED: - mListener.onDisplayRemoved(msg.arg1); + if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) { + mListener.onDisplayRemoved(msg.arg1); + } break; } } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index ff8a7208a9f0c..dee91445c2241 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -38,6 +38,7 @@ interface IDisplayManager { boolean isUidPresentOnDisplay(int uid, int displayId); void registerCallback(in IDisplayManagerCallback callback); + void registerCallbackWithEventMask(in IDisplayManagerCallback callback, long eventsMask); // Requires CONFIGURE_WIFI_DISPLAY permission. // The process must have previously registered a callback. diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java new file mode 100644 index 0000000000000..dfc9013e3c059 --- /dev/null +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; + +import android.content.Context; +import android.os.Handler; +import android.os.RemoteException; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DisplayManagerGlobalTest { + + private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + + @Mock + private IDisplayManager mDisplayManager; + + @Mock + private DisplayManager.DisplayListener mListener; + + @Captor + private ArgumentCaptor mCallbackCaptor; + + private Context mContext; + private DisplayManagerGlobal mDisplayManagerGlobal; + private Handler mHandler; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + Mockito.when(mDisplayManager.getPreferredWideGamutColorSpaceId()).thenReturn(0); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mHandler = mContext.getMainThreadHandler(); + mDisplayManagerGlobal = new DisplayManagerGlobal(mDisplayManager); + } + + @Test + public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException { + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); + IDisplayManagerCallback callback = mCallbackCaptor.getValue(); + + int displayId = 1; + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + waitForHandler(); + Mockito.verify(mListener).onDisplayAdded(eq(displayId)); + Mockito.verifyNoMoreInteractions(mListener); + + Mockito.reset(mListener); + callback.onDisplayEvent(1, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + waitForHandler(); + Mockito.verify(mListener).onDisplayChanged(eq(displayId)); + Mockito.verifyNoMoreInteractions(mListener); + + Mockito.reset(mListener); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + waitForHandler(); + Mockito.verify(mListener).onDisplayRemoved(eq(displayId)); + Mockito.verifyNoMoreInteractions(mListener); + } + + @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); + Mockito.verify(mDisplayManager) + .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); + IDisplayManagerCallback callback = mCallbackCaptor.getValue(); + + int displayId = 1; + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + waitForHandler(); + Mockito.verifyZeroInteractions(mListener); + + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + waitForHandler(); + Mockito.verifyZeroInteractions(mListener); + + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + waitForHandler(); + Mockito.verifyZeroInteractions(mListener); + } + + private void waitForHandler() { + mHandler.runWithScissors(() -> { }, 0); + } +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 82ca820ec4d74..f8a913aeb7cdf 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.hardware.display.DisplayManager.EventsMask; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; @@ -28,6 +29,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; +import static android.hardware.display.DisplayManagerGlobal.DisplayEvent; import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; @@ -125,6 +127,7 @@ import java.util.Arrays; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; /** @@ -820,14 +823,16 @@ private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) { } private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid, - int callingUid) { + int callingUid, @EventsMask long eventsMask) { synchronized (mSyncRoot) { - if (mCallbacks.get(callingPid) != null) { - throw new SecurityException("The calling process has already " - + "registered an IDisplayManagerCallback."); + CallbackRecord record = mCallbacks.get(callingPid); + + if (record != null) { + record.updateEventsMask(eventsMask); + return; } - CallbackRecord record = new CallbackRecord(callingPid, callingUid, callback); + record = new CallbackRecord(callingPid, callingUid, callback, eventsMask); try { IBinder binder = callback.asBinder(); binder.linkToDeath(record, 0); @@ -1695,7 +1700,7 @@ private void updateViewportPowerStateLocked(LogicalDisplay display) { } } - private void sendDisplayEventLocked(int displayId, int event) { + private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) { Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); mHandler.sendMessage(msg); } @@ -1724,7 +1729,8 @@ private void scheduleTraversalLocked(boolean inTraversal) { // Runs on Handler thread. // Delivers display event notifications to callbacks. - private void deliverDisplayEvent(int displayId, ArraySet uids, int event) { + private void deliverDisplayEvent(int displayId, ArraySet uids, + @DisplayEvent int event) { if (DEBUG) { Slog.d(TAG, "Delivering display event: displayId=" + displayId + ", event=" + event); @@ -2059,13 +2065,20 @@ private final class CallbackRecord implements DeathRecipient { public final int mPid; public final int mUid; private final IDisplayManagerCallback mCallback; + private @EventsMask AtomicLong mEventsMask; public boolean mWifiDisplayScanRequested; - CallbackRecord(int pid, int uid, IDisplayManagerCallback callback) { + CallbackRecord(int pid, int uid, IDisplayManagerCallback callback, + @EventsMask long eventsMask) { mPid = pid; mUid = uid; mCallback = callback; + mEventsMask = new AtomicLong(eventsMask); + } + + public void updateEventsMask(@EventsMask long eventsMask) { + mEventsMask.set(eventsMask); } @Override @@ -2076,7 +2089,11 @@ public void binderDied() { onCallbackDied(this); } - public void notifyDisplayEventAsync(int displayId, int event) { + public void notifyDisplayEventAsync(int displayId, @DisplayEvent int event) { + if (!shouldSendEvent(event)) { + return; + } + try { mCallback.onDisplayEvent(displayId, event); } catch (RemoteException ex) { @@ -2085,6 +2102,22 @@ public void notifyDisplayEventAsync(int displayId, int event) { binderDied(); } } + + private boolean shouldSendEvent(@DisplayEvent int event) { + final long mask = mEventsMask.get(); + switch (event) { + case DisplayManagerGlobal.EVENT_DISPLAY_ADDED: + return (mask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0; + case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: + return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0; + case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED: + return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0; + default: + // This should never happen. + Slog.e(TAG, "Unknown display event " + event); + return true; + } + } } @VisibleForTesting @@ -2149,6 +2182,14 @@ public Point getStableDisplaySize() { @Override // Binder call public void registerCallback(IDisplayManagerCallback callback) { + registerCallbackWithEventMask(callback, DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + } + + @Override // Binder call + public void registerCallbackWithEventMask(IDisplayManagerCallback callback, + @EventsMask long eventsMask) { if (callback == null) { throw new IllegalArgumentException("listener must not be null"); } @@ -2157,7 +2198,7 @@ public void registerCallback(IDisplayManagerCallback callback) { final int callingUid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - registerCallbackInternal(callback, callingPid, callingUid); + registerCallbackInternal(callback, callingPid, callingUid, eventsMask); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index bc86d1d39b1c2..429517251f671 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -93,7 +93,9 @@ public class DisplayManagerServiceTest { private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10; private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; - + private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @@ -355,29 +357,13 @@ public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exc // Find the display id of the added FakeDisplayDevice DisplayManagerService.BinderService bs = displayManager.new BinderService(); - final int[] displayIds = bs.getDisplayIds(); - assertTrue(displayIds.length > 0); - int displayId = Display.INVALID_DISPLAY; - for (int i = 0; i < displayIds.length; i++) { - DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayIds[i]); - if (displayDeviceInfo.equals(ddi)) { - displayId = displayIds[i]; - break; - } - } - assertFalse(displayId == Display.INVALID_DISPLAY); - + int displayId = getDisplayIdForDisplayDevice(displayManager, bs, displayDevice); // Setup override DisplayInfo DisplayInfo overrideInfo = bs.getDisplayInfo(displayId); displayManager.setDisplayInfoOverrideFromWindowManagerInternal(displayId, overrideInfo); - Handler handler = displayManager.getDisplayHandler(); - handler.runWithScissors(() -> { - }, 0 /* now */); - - // register display listener callback - FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId); - bs.registerCallback(callback); + FakeDisplayManagerCallback callback = registerDisplayListenerCallback( + displayManager, bs, displayDevice); // Simulate DisplayDevice change DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo(); @@ -387,9 +373,9 @@ public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exc displayManager.getDisplayDeviceRepository() .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED); - handler.runWithScissors(() -> { - }, 0 /* now */); - assertTrue(callback.mCalled); + Handler handler = displayManager.getDisplayHandler(); + waitForIdleHandler(handler); + assertTrue(callback.mDisplayChangedCalled); } /** @@ -400,7 +386,7 @@ public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exce DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); Handler handler = displayManager.getDisplayHandler(); - handler.runWithScissors(() -> {}, 0 /* now */); + waitForIdleHandler(handler); try { displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); @@ -616,7 +602,7 @@ public void testCreateVirtualDisplay_setSurface() throws Exception { } /** - * Tests that there should be a display change notification if the frame rate overrides + * Tests that there is a display change notification if the frame rate override * list is updated. */ @Test @@ -637,7 +623,7 @@ public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() thro new DisplayEventReceiver.FrameRateOverride[]{ new DisplayEventReceiver.FrameRateOverride(myUid, 30f), }); - assertTrue(callback.mCalled); + assertTrue(callback.mDisplayChangedCalled); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -645,7 +631,7 @@ public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() thro new DisplayEventReceiver.FrameRateOverride(myUid, 30f), new DisplayEventReceiver.FrameRateOverride(1234, 30f), }); - assertFalse(callback.mCalled); + assertFalse(callback.mDisplayChangedCalled); updateFrameRateOverride(displayManager, displayDevice, new DisplayEventReceiver.FrameRateOverride[]{ @@ -653,7 +639,7 @@ public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() thro new DisplayEventReceiver.FrameRateOverride(1234, 30f), new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); - assertTrue(callback.mCalled); + assertTrue(callback.mDisplayChangedCalled); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -661,14 +647,14 @@ public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() thro new DisplayEventReceiver.FrameRateOverride(1234, 30f), new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); - assertTrue(callback.mCalled); + assertTrue(callback.mDisplayChangedCalled); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, new DisplayEventReceiver.FrameRateOverride[]{ new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); - assertFalse(callback.mCalled); + assertFalse(callback.mDisplayChangedCalled); } /** @@ -760,6 +746,136 @@ public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception { /*compatChangeEnabled*/ true); } + /** + * Tests that EVENT_DISPLAY_ADDED is sent when a display is added. + */ + @Test + public void testShouldNotifyDisplayAdded_WhenNewDisplayDeviceIsAdded() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + + Handler handler = displayManager.getDisplayHandler(); + waitForIdleHandler(handler); + + // register display listener callback + FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); + displayManagerBinderService.registerCallbackWithEventMask(callback, ALL_DISPLAY_EVENTS); + + waitForIdleHandler(handler); + + createFakeDisplayDevice(displayManager, new float[]{60f}); + + waitForIdleHandler(handler); + + assertFalse(callback.mDisplayChangedCalled); + assertFalse(callback.mDisplayRemovedCalled); + assertTrue(callback.mDisplayAddedCalled); + } + + /** + * Tests that EVENT_DISPLAY_ADDED is not sent when a display is added and the + * client has a callback which is not subscribed to this event type. + */ + @Test + public void testShouldNotNotifyDisplayAdded_WhenClientIsNotSubscribed() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + + Handler handler = displayManager.getDisplayHandler(); + waitForIdleHandler(handler); + + // register display listener callback + FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); + long allEventsExceptDisplayAdded = ALL_DISPLAY_EVENTS + & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED; + displayManagerBinderService.registerCallbackWithEventMask(callback, + allEventsExceptDisplayAdded); + + waitForIdleHandler(handler); + + createFakeDisplayDevice(displayManager, new float[]{60f}); + + waitForIdleHandler(handler); + + assertFalse(callback.mDisplayChangedCalled); + assertFalse(callback.mDisplayRemovedCalled); + assertFalse(callback.mDisplayAddedCalled); + } + + /** + * Tests that EVENT_DISPLAY_REMOVED is sent when a display is removed. + */ + @Test + public void testShouldNotifyDisplayRemoved_WhenDisplayDeviceIsRemoved() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + + Handler handler = displayManager.getDisplayHandler(); + waitForIdleHandler(handler); + + FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, + new float[]{60f}); + + waitForIdleHandler(handler); + + FakeDisplayManagerCallback callback = registerDisplayListenerCallback( + displayManager, displayManagerBinderService, displayDevice); + + waitForIdleHandler(handler); + + displayManager.getDisplayDeviceRepository() + .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); + + waitForIdleHandler(handler); + + assertFalse(callback.mDisplayChangedCalled); + assertTrue(callback.mDisplayRemovedCalled); + assertFalse(callback.mDisplayAddedCalled); + } + + /** + * Tests that EVENT_DISPLAY_REMOVED is not sent when a display is added and the + * client has a callback which is not subscribed to this event type. + */ + @Test + public void testShouldNotNotifyDisplayRemoved_WhenClientIsNotSubscribed() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + + Handler handler = displayManager.getDisplayHandler(); + waitForIdleHandler(handler); + + FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, + new float[]{60f}); + + waitForIdleHandler(handler); + + FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); + long allEventsExceptDisplayRemoved = ALL_DISPLAY_EVENTS + & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + displayManagerBinderService.registerCallbackWithEventMask(callback, + allEventsExceptDisplayRemoved); + + waitForIdleHandler(handler); + + displayManager.getDisplayDeviceRepository() + .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); + + waitForIdleHandler(handler); + + assertFalse(callback.mDisplayChangedCalled); + assertFalse(callback.mDisplayRemovedCalled); + assertFalse(callback.mDisplayAddedCalled); + } + private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) throws Exception { DisplayManagerService displayManager = @@ -879,8 +995,7 @@ private void updateDisplayDeviceInfo(DisplayManagerService displayManager, displayManager.getDisplayDeviceRepository() .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED); Handler handler = displayManager.getDisplayHandler(); - handler.runWithScissors(() -> { - }, 0 /* now */); + waitForIdleHandler(handler); } private void updateFrameRateOverride(DisplayManagerService displayManager, @@ -906,18 +1021,15 @@ private FakeDisplayManagerCallback registerDisplayListenerCallback( DisplayManagerService.BinderService displayManagerBinderService, FakeDisplayDevice displayDevice) { // Find the display id of the added FakeDisplayDevice - DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); - int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService, displayDevice); Handler handler = displayManager.getDisplayHandler(); - handler.runWithScissors(() -> { - }, 0 /* now */); + waitForIdleHandler(handler); // register display listener callback FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId); - displayManagerBinderService.registerCallback(callback); + displayManagerBinderService.registerCallbackWithEventMask(callback, ALL_DISPLAY_EVENTS); return callback; } @@ -951,6 +1063,10 @@ private void registerDefaultDisplays(DisplayManagerService displayManager) { // Would prefer to call displayManager.onStart() directly here but it performs binderService // registration which triggers security exceptions when running from a test. handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS); + waitForIdleHandler(handler); + } + + private void waitForIdleHandler(Handler handler) { waitForIdleHandler(handler, Duration.ofSeconds(1)); } @@ -971,21 +1087,41 @@ private void waitForIdleHandler(Handler handler, Duration timeout) { private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub { int mDisplayId; - boolean mCalled = false; + boolean mDisplayAddedCalled = false; + boolean mDisplayChangedCalled = false; + boolean mDisplayRemovedCalled = false; FakeDisplayManagerCallback(int displayId) { mDisplayId = displayId; } + FakeDisplayManagerCallback() { + mDisplayId = -1; + } + @Override public void onDisplayEvent(int displayId, int event) { - if (displayId == mDisplayId && event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) { - mCalled = true; + if (mDisplayId != -1 && displayId != mDisplayId) { + return; + } + + if (event == DisplayManagerGlobal.EVENT_DISPLAY_ADDED) { + mDisplayAddedCalled = true; + } + + if (event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) { + mDisplayChangedCalled = true; + } + + if (event == DisplayManagerGlobal.EVENT_DISPLAY_REMOVED) { + mDisplayRemovedCalled = true; } } public void clear() { - mCalled = false; + mDisplayAddedCalled = false; + mDisplayChangedCalled = false; + mDisplayRemovedCalled = false; } }