• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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