1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.display.cts; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertNull; 21 import static org.junit.Assert.fail; 22 import static org.junit.Assume.assumeTrue; 23 24 import android.Manifest; 25 import android.app.Instrumentation; 26 import android.content.Context; 27 import android.hardware.display.DisplayManager; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.Messenger; 33 import android.os.PowerManager; 34 import android.os.RemoteException; 35 import android.platform.test.annotations.AppModeSdkSandbox; 36 import android.platform.test.annotations.RequiresFlagsDisabled; 37 import android.platform.test.annotations.RequiresFlagsEnabled; 38 import android.platform.test.flag.junit.CheckFlagsRule; 39 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 40 import android.server.wm.UiDeviceUtils; 41 import android.util.Log; 42 import android.util.Pair; 43 import android.view.Display; 44 45 import androidx.test.ext.junit.rules.ActivityScenarioRule; 46 import androidx.test.platform.app.InstrumentationRegistry; 47 import androidx.test.runner.AndroidJUnit4; 48 49 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 50 import com.android.server.display.feature.flags.Flags; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Rule; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 import java.util.Arrays; 59 import java.util.concurrent.LinkedBlockingQueue; 60 import java.util.concurrent.TimeUnit; 61 62 /** 63 * Tests that applications can receive display events correctly. 64 */ 65 @RunWith(AndroidJUnit4.class) 66 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 67 public class DisplayEventTest extends TestBase { 68 private static final float RR_FLOAT_DELTA = 0.01f; 69 private static final String TAG = "DisplayEventTest"; 70 71 private static final int MESSAGE_CALLBACK = 1; 72 73 private static final long TEST_FAILURE_TIMEOUT_MSEC = 5000; 74 75 private static final int DISPLAY_ADDED = 1; 76 private static final int DISPLAY_CHANGED = 2; 77 private static final int DISPLAY_REMOVED = 3; 78 79 private final Object mLock = new Object(); 80 81 private Instrumentation mInstrumentation; 82 private Context mContext; 83 private DisplayManager mDisplayManager; 84 85 private PowerManager mPowerManager; 86 87 private Display mDisplay; 88 89 @Rule 90 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 91 92 @Rule 93 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 94 InstrumentationRegistry.getInstrumentation().getUiAutomation(), 95 Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS, 96 Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE, 97 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 98 99 100 @Rule 101 public ActivityScenarioRule<DisplayEventPropertyChangeActivity> mActivityRule = 102 new ActivityScenarioRule<>(DisplayEventPropertyChangeActivity.class); 103 104 private HandlerThread mHandlerThread; 105 private Handler mHandler; 106 private Messenger mMessenger; 107 private final LinkedBlockingQueue<Pair<Integer, Integer>> mExpectations = 108 new LinkedBlockingQueue<>(); 109 private DisplayManager.DisplayListener mDisplayListener; 110 111 @Before setUp()112 public void setUp() throws Exception { 113 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 114 mContext = mInstrumentation.getContext(); 115 mDisplayManager = mContext.getSystemService(DisplayManager.class); 116 mPowerManager = mContext.getSystemService(PowerManager.class); 117 mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 118 mHandlerThread = new HandlerThread("handler"); 119 mHandlerThread.start(); 120 mHandler = new TestHandler(mHandlerThread.getLooper()); 121 mMessenger = new Messenger(mHandler); 122 } 123 124 @After tearDown()125 public void tearDown() throws Exception { 126 mHandlerThread.quitSafely(); 127 if (mDisplayListener != null) { 128 mDisplayManager.unregisterDisplayListener(mDisplayListener); 129 } 130 } 131 132 @Test 133 @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) testDisplayStateChangedEvent()134 public void testDisplayStateChangedEvent() { 135 registerDisplayListener((int) DisplayManager.EVENT_TYPE_DISPLAY_STATE); 136 137 // Change the display state 138 switchDisplayState(); 139 140 // Validate the event was received 141 waitDisplayEvent(Display.DEFAULT_DISPLAY, DISPLAY_CHANGED); 142 143 // Change the display state 144 switchDisplayState(); 145 146 // Validate the event was received 147 waitDisplayEvent(Display.DEFAULT_DISPLAY, DISPLAY_CHANGED); 148 } 149 150 @Test 151 @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) testDisplayRefreshRateChangedEvent()152 public void testDisplayRefreshRateChangedEvent() throws InterruptedException { 153 assumeTrue(notInConcurrentDisplayState()); 154 registerDisplayListener((int) DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE); 155 156 switchRefreshRate(); 157 158 waitDisplayEvent(Display.DEFAULT_DISPLAY, DISPLAY_CHANGED); 159 } 160 161 @Test testNoDisplayRefreshRateChangedEvent()162 public void testNoDisplayRefreshRateChangedEvent() throws InterruptedException { 163 assumeTrue(notInConcurrentDisplayState()); 164 registerDisplayListener((int) DisplayManager.EVENT_TYPE_DISPLAY_CHANGED); 165 166 switchRefreshRate(); 167 168 assertNoDisplayEventEmitted(); 169 } 170 171 @Test 172 @RequiresFlagsDisabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED) test_displayRrChangedEvent_delayImplicitRegistrationUntilRrAccessedDisabled()173 public void test_displayRrChangedEvent_delayImplicitRegistrationUntilRrAccessedDisabled() 174 throws InterruptedException { 175 assumeTrue(notInConcurrentDisplayState()); 176 registerDisplayListener(); 177 178 switchRefreshRate(); 179 180 waitDisplayEvent(Display.DEFAULT_DISPLAY, DISPLAY_CHANGED); 181 } 182 183 @Test 184 @RequiresFlagsEnabled(Flags.FLAG_DELAY_IMPLICIT_RR_REGISTRATION_UNTIL_RR_ACCESSED) test_noDisplayRrChangedEvent_delayImplicitRegistrationUntilRrAccessedEnabled()185 public void test_noDisplayRrChangedEvent_delayImplicitRegistrationUntilRrAccessedEnabled() 186 throws InterruptedException { 187 assumeTrue(notInConcurrentDisplayState()); 188 189 // Reset the implicit RR callbacks registration 190 mDisplayManager.resetImplicitRefreshRateCallbackStatus(); 191 192 registerDisplayListener(); 193 switchRefreshRate(); 194 assertNoDisplayEventEmitted(); 195 196 // This tells DisplayManager that the client is interested in refresh rate data, so register 197 // them for refresh rate change callbacks 198 mDisplay.getRefreshRate(); 199 switchRefreshRate(); 200 waitDisplayEvent(Display.DEFAULT_DISPLAY, DISPLAY_CHANGED); 201 } 202 notInConcurrentDisplayState()203 boolean notInConcurrentDisplayState() { 204 long invalidDisplayStatesCount = Arrays.stream(mDisplayManager.getDisplays()) 205 .filter(display -> (display.getDisplayId() == Display.DEFAULT_DISPLAY 206 && display.getState() != Display.STATE_ON) 207 || (display.getDisplayId() != Display.DEFAULT_DISPLAY 208 && display.getState() == Display.STATE_ON)) 209 .count(); 210 return invalidDisplayStatesCount == 0; 211 } 212 registerDisplayListener(int eventFlagMask)213 private void registerDisplayListener(int eventFlagMask) { 214 initDisplayListener(); 215 mDisplayManager.registerDisplayListener( 216 mContext.getMainExecutor(), eventFlagMask, mDisplayListener); 217 } 218 initDisplayListener()219 private void initDisplayListener() { 220 mDisplayListener = new DisplayManager.DisplayListener() { 221 @Override 222 public void onDisplayAdded(int displayId) { 223 callback(displayId, DISPLAY_ADDED); 224 } 225 226 @Override 227 public void onDisplayRemoved(int displayId) { 228 callback(displayId, DISPLAY_REMOVED); 229 } 230 231 @Override 232 public void onDisplayChanged(int displayId) { 233 callback(displayId, DISPLAY_CHANGED); 234 } 235 }; 236 } 237 registerDisplayListener()238 private void registerDisplayListener() { 239 initDisplayListener(); 240 mDisplayManager.registerDisplayListener( 241 mDisplayListener, new Handler(Looper.getMainLooper())); 242 } 243 244 /** 245 * Add the received display event from the test activity to the queue 246 * 247 * @param event The corresponding display event 248 */ addDisplayEvent(int displayId, int event)249 private void addDisplayEvent(int displayId, int event) { 250 Log.d(TAG, "Received " + displayId + " " + event); 251 mExpectations.offer(new Pair<>(displayId, event)); 252 } 253 254 /** 255 * Wait for the expected display event from the test activity 256 * 257 * @param expect The expected display event 258 */ waitDisplayEvent(int displayId, int expect)259 private void waitDisplayEvent(int displayId, int expect) { 260 while (true) { 261 try { 262 Pair<Integer, Integer> expectedPair = new Pair<>(displayId, expect); 263 Pair<Integer, Integer> event = 264 mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS); 265 assertNotNull(event); 266 if (expectedPair.equals(event)) { 267 return; 268 } 269 } catch (InterruptedException e) { 270 throw new RuntimeException(e); 271 } 272 } 273 } 274 275 /** Validates that no events are emitted */ assertNoDisplayEventEmitted()276 private void assertNoDisplayEventEmitted() { 277 try { 278 Pair<Integer, Integer> event = 279 mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS); 280 assertNull(event); 281 } catch (InterruptedException e) { 282 throw new RuntimeException(e); 283 } 284 } 285 286 /** Flushes all the display events received soo far */ flushDisplayEventsQueue()287 private void flushDisplayEventsQueue() { 288 mExpectations.clear(); 289 } 290 switchDisplayState()291 private void switchDisplayState() { 292 if (!mPowerManager.isInteractive()) { 293 UiDeviceUtils.pressWakeupButton(); 294 } else { 295 UiDeviceUtils.pressSleepButton(); 296 } 297 } 298 switchRefreshRate()299 private void switchRefreshRate() throws InterruptedException { 300 UiDeviceUtils.pressSleepButton(); 301 UiDeviceUtils.pressWakeupButton(); 302 int mInitialMatchContentFrameRate = 303 toSwitchingType(mDisplayManager.getMatchContentFrameRateUserPreference()); 304 setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE, true); 305 flushDisplayEventsQueue(); 306 307 int alternateRefreshRateModeId = getAlternateRefreshRateModeId(); 308 mActivityRule.getScenario().onActivity(activity -> { 309 activity.setModeId(alternateRefreshRateModeId); 310 }); 311 312 setRefreshRateSwitchingType(mInitialMatchContentFrameRate, false); 313 } 314 setRefreshRateSwitchingType(int switchingType, boolean appRequestedMode)315 private void setRefreshRateSwitchingType(int switchingType, boolean appRequestedMode) 316 throws InterruptedException { 317 mDisplayManager.setRefreshRateSwitchingType(switchingType); 318 mDisplayManager.setShouldAlwaysRespectAppRequestedMode(appRequestedMode); 319 320 // Wait for DisplayModeDirector to notify sf about the changes to the switching type 321 Thread.sleep(2000); 322 } 323 getAlternateRefreshRateModeId()324 private int getAlternateRefreshRateModeId() { 325 int refreshRateModeId = mDisplay.getMode().getModeId(); 326 boolean supportsMultipleRefreshRates = false; 327 for (Display.Mode mode : mDisplay.getSupportedModes()) { 328 if (mode.getModeId() == mDisplay.getMode().getModeId()) { 329 continue; 330 } 331 332 if (mode.getPhysicalHeight() != mDisplay.getMode().getPhysicalHeight()) { 333 continue; 334 } 335 336 if (mode.getPhysicalWidth() != mDisplay.getMode().getPhysicalWidth()) { 337 continue; 338 } 339 340 if (mode.isSynthetic()) { 341 continue; 342 } 343 344 if (!floatEquals(mode.getRefreshRate(), mDisplay.getMode().getRefreshRate(), 345 RR_FLOAT_DELTA)) { 346 supportsMultipleRefreshRates = true; 347 refreshRateModeId = mode.getModeId(); 348 } 349 } 350 assumeTrue(supportsMultipleRefreshRates); 351 return refreshRateModeId; 352 } 353 floatEquals(float f1, float f2, float delta)354 private boolean floatEquals(float f1, float f2, float delta) { 355 return Math.abs(f1 - f2) <= delta; 356 } 357 toSwitchingType(int matchContentFrameRateUserPreference)358 private static int toSwitchingType(int matchContentFrameRateUserPreference) { 359 switch (matchContentFrameRateUserPreference) { 360 case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER: 361 return DisplayManager.SWITCHING_TYPE_NONE; 362 case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY: 363 return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; 364 case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS: 365 return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; 366 default: 367 return -1; 368 } 369 } 370 371 private class TestHandler extends Handler { TestHandler(Looper looper)372 TestHandler(Looper looper) { 373 super(looper); 374 } 375 376 @Override handleMessage(Message msg)377 public void handleMessage(Message msg) { 378 switch (msg.what) { 379 case MESSAGE_CALLBACK: 380 synchronized (mLock) { 381 addDisplayEvent(msg.arg1, msg.arg2); 382 } 383 break; 384 default: 385 fail("Unexpected value: " + msg.what); 386 break; 387 } 388 } 389 } 390 callback(int displayId, int event)391 private void callback(int displayId, int event) { 392 try { 393 Message msg = Message.obtain(); 394 msg.what = MESSAGE_CALLBACK; 395 msg.arg1 = displayId; 396 msg.arg2 = event; 397 Log.d(TAG, "Msg " + msg.arg1 + " " + msg.arg2); 398 mMessenger.send(msg); 399 } catch (RemoteException e) { 400 throw new RuntimeException(e); 401 } 402 } 403 } 404