1 /*
2  * Copyright (C) 2023 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.server.wm.activity;
18 
19 import static android.view.Display.DEFAULT_DISPLAY;
20 
21 import static junit.framework.Assert.assertFalse;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertNotEquals;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assume.assumeFalse;
27 import static org.junit.Assume.assumeTrue;
28 
29 import android.content.ComponentCallbacks;
30 import android.content.res.Configuration;
31 import android.graphics.Rect;
32 import android.hardware.display.DisplayManager;
33 import android.hardware.display.DisplayManager.DisplayListener;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.RemoteException;
37 import android.os.SystemClock;
38 import android.platform.test.annotations.Presubmit;
39 import android.platform.test.annotations.RequiresFlagsEnabled;
40 import android.platform.test.flag.junit.CheckFlagsRule;
41 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
42 import android.server.wm.RotationSession;
43 import android.server.wm.WindowManagerTestBase;
44 import android.util.Log;
45 import android.util.Size;
46 import android.view.Display;
47 
48 import androidx.annotation.NonNull;
49 
50 import com.android.compatibility.common.util.ApiTest;
51 import com.android.window.flags.Flags;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 
58 /**
59  * Tests that verify the behavior of client side window configuration related state changed
60  * callbacks, such as {@link DisplayListener}, to ensure that they are synchronized with the client
61  * side {@link Configuration} change.
62  *
63  * Build/Install/Run:
64  *     atest CtsWindowManagerDeviceActivity:ConfigurationCallbacksTest
65  */
66 @Presubmit
67 public class ConfigurationCallbacksTest extends WindowManagerTestBase {
68 
69     @Rule
70     public final CheckFlagsRule mCheckFlagsRule =
71             DeviceFlagsValueProvider.createCheckFlagsRule();
72 
73     private static final String TAG = ConfigurationCallbacksTest.class.getSimpleName();
74 
75     private ReportedDisplayMetrics mReportedDisplayMetrics;
76 
77     private WindowConfigTracker mDisplayListenerTracker;
78     private WindowConfigTracker mActivityOnConfigurationChangedTracker;
79     private WindowConfigTracker mApplicationOnConfigurationChangedTracker;
80 
81     private TestComponentCallbacks mApplicationCallbacks;
82     private TestDisplayListener mDisplayListener;
83     private TestActivity mActivity;
84 
85     @Before
setUp()86     public void setUp() throws Exception {
87         super.setUp();
88 
89         mReportedDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(Display.DEFAULT_DISPLAY);
90 
91         mDisplayListenerTracker = new WindowConfigTracker("DisplayListener");
92         mActivityOnConfigurationChangedTracker = new WindowConfigTracker(
93                 "Activity#onConfigurationChanged");
94         // Application callback is expected to be triggered before Activity Config update.
95         mApplicationOnConfigurationChangedTracker = new WindowConfigTracker(
96                 "Application#onConfigurationChanged", true /* excludeActivity */);
97 
98         mActivity = startActivityInWindowingModeFullScreen(TestActivity.class);
99         waitAndAssertResumedActivity(mActivity.getComponentName(), "The activity must be resumed.");
100 
101         mActivity.setWindowConfigTracker(mActivityOnConfigurationChangedTracker);
102 
103         mApplicationCallbacks = new TestComponentCallbacks(
104                 mApplicationOnConfigurationChangedTracker);
105         mActivity.getApplication().registerComponentCallbacks(mApplicationCallbacks);
106 
107         mDisplayListener = new TestDisplayListener(mDisplayListenerTracker);
108         mDm.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
109     }
110 
111     @After
tearDown()112     public void tearDown() throws RemoteException {
113         if (mDisplayListener != null) {
114             mDm.unregisterDisplayListener(mDisplayListener);
115         }
116         if (mActivity != null) {
117             mActivity.getApplication().unregisterComponentCallbacks(mApplicationCallbacks);
118             mActivity.finish();
119         }
120         if (mReportedDisplayMetrics != null) {
121             mReportedDisplayMetrics.restoreDisplayMetrics();
122         }
123     }
124 
125     /**
126      * Verifies that when the display rotates, the last triggered
127      * {@link DisplayListener#onDisplayChanged} have updated {@link android.app.WindowConfiguration}
128      * that is synchronized with the display window.
129      */
130     @RequiresFlagsEnabled(Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG)
131     @Test
132     @ApiTest(apis = {
133             "android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged",
134             "android.app.Activity#onConfigurationChanged",
135             "android.content.ComponentCallbacks#onConfigurationChanged",
136     })
testDisplayRotate()137     public void testDisplayRotate() {
138         assumeTrue(supportsRotation());
139         // Devices that always launch activities in multi-window may not be able to update
140         // Task bounds in the same transaction with display bounds (maxBounds) changed.
141         assumeFalse(mActivity.isInMultiWindowMode());
142 
143         final RotationSession rotationSession = createManagedRotationSession();
144         int rotation = rotationSession.get();
145         for (int i = 0; i < 4; i++) {
146             rotation = (rotation + 1) % 4;
147             initTrackers();
148             rotationSession.set(rotation);
149             waitAndAssertRotationInCallbacks(rotation);
150         }
151     }
152 
153     /**
154      * Verifies that when the display resizes, the last triggered
155      * {@link DisplayListener#onDisplayChanged} have updated {@link android.app.WindowConfiguration}
156      * that is synchronized with the display window.
157      */
158     @RequiresFlagsEnabled(Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG)
159     @Test
160     @ApiTest(apis = {
161             "android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged",
162             "android.app.Activity#onConfigurationChanged",
163             "android.content.ComponentCallbacks#onConfigurationChanged",
164     })
testDisplayResize()165     public void testDisplayResize() {
166         // Devices that always launch activities in multi-window may not be able to update
167         // Task bounds in the same transaction with display bounds (maxBounds) changed.
168         assumeFalse(mActivity.isInMultiWindowMode());
169 
170         final Size originalSize = mReportedDisplayMetrics.getSize();
171         // Use a negative offset in case the device set config_maxUiWidth.
172         final int offset = -Math.min(originalSize.getWidth() / 10, originalSize.getHeight() / 10);
173         final int newWidth = originalSize.getWidth() + offset;
174         final int newHeight = originalSize.getHeight() + offset;
175         assumeTrue("Can't resize the display smaller than min size",
176                 newWidth >= 200 && newHeight >= 200);
177 
178         initTrackers();
179         mReportedDisplayMetrics.setSize(new Size(newWidth, newHeight));
180         if (hasAutomotiveSplitscreenMultitaskingFeature()) {
181             // On Automotive SplitScreen Multitasking devices, the bounds of TDAs (& thus
182             // activities) may not grow linearly with the change in display bounds.
183             waitAndAssertBoundsChangeInCallbacks();
184         } else {
185             waitAndAssertDimensionsOffsetInCallbacks(offset);
186         }
187     }
188 
189     /**
190      * Similar to {@link #testDisplayResize()}, but works for devices that always launch activities
191      * in multi-window to make sure the display bounds is always up-to-date.
192      */
193     @RequiresFlagsEnabled(Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG)
194     @Test
195     @ApiTest(apis = {
196             "android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged",
197             "android.app.Activity#onConfigurationChanged",
198             "android.content.ComponentCallbacks#onConfigurationChanged",
199     })
testDisplayResizeForDisplayBoundsOnly()200     public void testDisplayResizeForDisplayBoundsOnly() {
201         final Size originalSize = mReportedDisplayMetrics.getSize();
202         // Use a negative offset in case the device set config_maxUiWidth.
203         final int offset = -Math.min(originalSize.getWidth() / 10, originalSize.getHeight() / 10);
204         final int newWidth = originalSize.getWidth() + offset;
205         final int newHeight = originalSize.getHeight() + offset;
206         assumeTrue("Can't resize the display smaller than min size",
207                 newWidth >= 200 && newHeight >= 200);
208 
209         initTrackers();
210         mReportedDisplayMetrics.setSize(new Size(newWidth, newHeight));
211 
212         if (hasAutomotiveSplitscreenMultitaskingFeature()) {
213             // On Automotive SplitScreen Multitasking devices, the bounds of TDAs (& thus
214             // activities) may not grow linearly with the change in display bounds.
215             waitAndAssertDisplayBoundsChangeInCallbacks();
216         } else {
217             waitAndAssertDisplayOffsetInCallbacks(offset);
218         }
219     }
220 
221     /**
222      * Initializes {@link WindowConfigTracker}s.
223      * Should be called before triggering the test system event.
224      */
initTrackers()225     private void initTrackers() {
226         Log.d(TAG, "initTrackers");
227         mDisplayListenerTracker.init();
228         mActivityOnConfigurationChangedTracker.init();
229         mApplicationOnConfigurationChangedTracker.init();
230     }
231 
232     /**
233      * Waits and asserts that the last system callbacks must come with the given display rotation.
234      */
waitAndAssertRotationInCallbacks(int expectedRotation)235     private void waitAndAssertRotationInCallbacks(int expectedRotation) {
236         final long curTime = SystemClock.elapsedRealtime();
237         mDisplayListenerTracker.waitAndAssertRotation(expectedRotation, curTime);
238         mActivityOnConfigurationChangedTracker.waitAndAssertRotation(expectedRotation, curTime);
239         mApplicationOnConfigurationChangedTracker.waitAndAssertRotation(expectedRotation, curTime);
240     }
241 
242     /**
243      * Waits and asserts that the last system callbacks must come with display dimensions which are
244      * different from the initial dimensions.
245      */
waitAndAssertBoundsChangeInCallbacks()246     private void waitAndAssertBoundsChangeInCallbacks() {
247         final long curTime = SystemClock.elapsedRealtime();
248         mDisplayListenerTracker.waitAndAssertBoundsChange(curTime);
249         mActivityOnConfigurationChangedTracker.waitAndAssertBoundsChange(curTime);
250         mApplicationOnConfigurationChangedTracker.waitAndAssertBoundsChange(curTime);
251     }
252 
253     /**
254      * Waits and asserts that the last system callbacks must come with display dimensions with the
255      * given offset from the current dimensions.
256      *
257      * Note: the same offset will be used for both width and height because the display size set
258      * through {@link ReportedDisplayMetrics} is independent to the display rotation.
259      */
waitAndAssertDimensionsOffsetInCallbacks(int expectedOffset)260     private void waitAndAssertDimensionsOffsetInCallbacks(int expectedOffset) {
261         final long curTime = SystemClock.elapsedRealtime();
262         mDisplayListenerTracker.waitAndAssertDimensionsOffset(expectedOffset, curTime);
263         mActivityOnConfigurationChangedTracker.waitAndAssertDimensionsOffset(expectedOffset,
264                 curTime);
265         mApplicationOnConfigurationChangedTracker.waitAndAssertDimensionsOffset(expectedOffset,
266                 curTime);
267     }
268 
269     /**
270      * Similar to {@link #waitAndAssertBoundsChangeInCallbacks()}, but only verify display bounds.
271      */
waitAndAssertDisplayBoundsChangeInCallbacks()272     private void waitAndAssertDisplayBoundsChangeInCallbacks() {
273         final long curTime = SystemClock.elapsedRealtime();
274         mDisplayListenerTracker.waitAndAssertDisplayBoundsChange(curTime);
275         mActivityOnConfigurationChangedTracker.waitAndAssertDisplayBoundsChange(curTime);
276         mApplicationOnConfigurationChangedTracker.waitAndAssertDisplayBoundsChange(curTime);
277     }
278 
279     /**
280      * Similar to {@link #waitAndAssertDimensionsOffsetInCallbacks}, but only verify display bounds.
281      */
waitAndAssertDisplayOffsetInCallbacks(int expectedOffset)282     private void waitAndAssertDisplayOffsetInCallbacks(int expectedOffset) {
283         final long curTime = SystemClock.elapsedRealtime();
284         mDisplayListenerTracker.waitAndAssertDisplayOffset(expectedOffset, curTime);
285         mActivityOnConfigurationChangedTracker.waitAndAssertDisplayOffset(expectedOffset,
286                 curTime);
287         mApplicationOnConfigurationChangedTracker.waitAndAssertDisplayOffset(expectedOffset,
288                 curTime);
289     }
290 
291     /**
292      * Helper class to keep track and assert the window configuration in system callbacks.
293      *
294      * Use flow:
295      * 1. Calls {@link #init()} to reset tracked value, and record the initial config.
296      * 2. Applies any action to trigger system callbacks.
297      * 3. Calls {@link #waitAndAssertRotation} or {@link #waitAndAssertDimensionsOffset}
298      *    to test that the last system callback has the current config.
299      */
300     private class WindowConfigTracker {
301 
302         private static final int INVALID_ROTATION = -1;
303 
304         private static final long CALLBACK_TIMEOUT_MS = 2000L;
305         private static final long CALLBACK_TIMEOUT_MAX_RETRY = 4;
306         private static final long CALLBACK_TIMEOUT_MAX_WAITING_TIME_MS = 8000L; // TIMEOUT * RETRY
307 
308         @NonNull
309         private final String mCallbackName;
310         private final boolean mExcludeActivity;
311 
312         /** How many times the callback has been triggered since the last {@link #init()} */
313         private int mCallbackCount;
314 
315         /** The system time when the last system callback was triggered. */
316         private long mLastCallbackSystemTime;
317 
318         private int mInitDisplayRotation = INVALID_ROTATION;
319         private int mInitActivityRotation = INVALID_ROTATION;
320         private int mInitApplicationRotation = INVALID_ROTATION;
321         private final Rect mInitWindowMetricsBounds = new Rect();
322         private final Rect mInitActivityBounds = new Rect();
323         private final Rect mInitApplicationBounds = new Rect();
324 
325         private int mLastDisplayRotation = INVALID_ROTATION;
326         private int mLastActivityRotation = INVALID_ROTATION;
327         private int mLastApplicationRotation = INVALID_ROTATION;
328         private final Rect mLastWindowMetricsBounds = new Rect();
329         private final Rect mLastActivityBounds = new Rect();
330         private final Rect mLastApplicationBounds = new Rect();
331 
WindowConfigTracker(@onNull String callbackName)332         WindowConfigTracker(@NonNull String callbackName) {
333             this(callbackName, false /* excludeActivity */);
334         }
335 
WindowConfigTracker(@onNull String callbackName, boolean excludeActivity)336         WindowConfigTracker(@NonNull String callbackName, boolean excludeActivity) {
337             mCallbackName = callbackName;
338             mExcludeActivity = excludeActivity;
339         }
340 
341         /** Should be called before triggering the test system event. */
init()342         void init() {
343             // Reset counter and timer
344             mCallbackCount = 0;
345             mLastCallbackSystemTime = 0;
346 
347             // Record the current config
348             mInitDisplayRotation = getDisplayRotation();
349             mInitActivityRotation = getActivityRotation();
350             mInitApplicationRotation = getApplicationRotation();
351             mInitWindowMetricsBounds.set(getWindowMetricsBounds());
352             mInitActivityBounds.set(getActivityBounds());
353             mInitApplicationBounds.set(getApplicationBounds());
354 
355             // Reset the config from last system callback.
356             mLastDisplayRotation = INVALID_ROTATION;
357             mLastActivityRotation = INVALID_ROTATION;
358             mLastApplicationRotation = INVALID_ROTATION;
359             mLastWindowMetricsBounds.setEmpty();
360             mLastActivityBounds.setEmpty();
361             mLastApplicationBounds.setEmpty();
362         }
363 
364         /**
365          * Called when there is a system callback regarding the window config changed.
366          */
onWindowConfigChanged()367         void onWindowConfigChanged() {
368             mCallbackCount++;
369             mLastCallbackSystemTime = SystemClock.elapsedRealtime();
370 
371             mLastDisplayRotation = getDisplayRotation();
372             mLastActivityRotation = getActivityRotation();
373             mLastApplicationRotation = getApplicationRotation();
374             mLastWindowMetricsBounds.set(getWindowMetricsBounds());
375             mLastActivityBounds.set(getActivityBounds());
376             mLastApplicationBounds.set(getApplicationBounds());
377         }
378 
379         /**
380          * Waits and asserts that the last system callback must come with the given display
381          * rotation.
382          */
waitAndAssertRotation(int expectedRotation, long startTime)383         void waitAndAssertRotation(int expectedRotation, long startTime) {
384             waitForLastCallbackTimeout(startTime);
385             assertCallbackTriggered();
386 
387             final String errorMessage = mCallbackName
388                     + ": expect the last rotation to be "
389                     + expectedRotation + ", but have:"
390                     + "\ninitDisplayRotation=" + mInitDisplayRotation
391                     + "\ninitActivityRotation=" + mInitActivityRotation
392                     + "\ninitApplicationRotation=" + mInitApplicationRotation
393                     + "\nlastDisplayRotation=" + mLastDisplayRotation
394                     + "\nlastActivityRotation=" + mLastActivityRotation
395                     + "\nlastApplicationRotation=" + mLastApplicationRotation
396                     + "\nThe callback has been triggered for " + mCallbackCount + " times.";
397             assertTrue(errorMessage, mLastDisplayRotation == expectedRotation
398                     && (mExcludeActivity || mLastActivityRotation == expectedRotation)
399                     && mLastApplicationRotation == expectedRotation);
400         }
401 
402         /**
403          * Waits and asserts that the last system callback must come with
404          * display/application/activity dimensions which is different from the initial dimensions.
405          */
waitAndAssertBoundsChange(long startTime)406         void waitAndAssertBoundsChange(long startTime) {
407             waitForLastCallbackTimeout(startTime);
408             assertCallbackTriggered();
409 
410             final String errorMessage = mCallbackName
411                     + ": expect the bounds to change, but have:"
412                     + "\ninitDisplayBounds=" + mInitWindowMetricsBounds
413                     + "\ninitActivityBounds=" + mInitActivityBounds
414                     + "\ninitApplicationBounds=" + mInitApplicationBounds
415                     + "\nlastDisplayBounds=" + mLastWindowMetricsBounds
416                     + "\nlastActivityBounds=" + mLastActivityBounds
417                     + "\nlastApplicationBounds=" + mLastApplicationBounds
418                     + "\nThe callback has been triggered for " + mCallbackCount + " times.";
419             assertFalse(errorMessage, mInitWindowMetricsBounds.equals(mLastWindowMetricsBounds)
420                     || (!mExcludeActivity && mInitActivityBounds.equals(mLastActivityBounds))
421                     || mInitApplicationBounds.equals(mLastApplicationBounds));
422         }
423 
424         /**
425          * Waits and asserts that the last system callback must come with
426          * display/application/activity dimensions with the given offset from the initial
427          * dimensions.
428          *
429          * Note: the same offset will be used for both width and height because the display size set
430          * through {@link ReportedDisplayMetrics} is independent to the display rotation.
431          */
waitAndAssertDimensionsOffset(int expectedOffset, long startTime)432         void waitAndAssertDimensionsOffset(int expectedOffset, long startTime) {
433             waitForLastCallbackTimeout(startTime);
434             assertCallbackTriggered();
435 
436             final String errorMessage = mCallbackName
437                     + ": expect the offset from last bounds right/bottom to be "
438                     + expectedOffset + ", but have:"
439                     + "\ninitDisplayBounds=" + mInitWindowMetricsBounds
440                     + "\ninitActivityBounds=" + mInitActivityBounds
441                     + "\ninitApplicationBounds=" + mInitApplicationBounds
442                     + "\nlastDisplayBounds=" + mLastWindowMetricsBounds
443                     + "\nlastActivityBounds=" + mLastActivityBounds
444                     + "\nlastApplicationBounds=" + mLastApplicationBounds
445                     + "\nThe callback has been triggered for " + mCallbackCount + " times.";
446             mInitWindowMetricsBounds.right += expectedOffset;
447             mInitWindowMetricsBounds.bottom += expectedOffset;
448             mInitActivityBounds.right += expectedOffset;
449             mInitActivityBounds.bottom += expectedOffset;
450             mInitApplicationBounds.right += expectedOffset;
451             mInitApplicationBounds.bottom += expectedOffset;
452             assertTrue(errorMessage, mInitWindowMetricsBounds.equals(mLastWindowMetricsBounds)
453                     && (mExcludeActivity || mInitActivityBounds.equals(mLastActivityBounds))
454                     && mInitApplicationBounds.equals(mLastApplicationBounds));
455         }
456 
457         /**
458          * Similar to {@link #waitAndAssertBoundsChange(long)}, but only verify display
459          * bounds.
460          */
waitAndAssertDisplayBoundsChange(long startTime)461         void waitAndAssertDisplayBoundsChange(long startTime) {
462             waitForLastCallbackTimeout(startTime);
463             assertCallbackTriggered();
464 
465             final String errorMessage = mCallbackName
466                     + ": expect the last display bounds to be different from initial bounds, "
467                     + "but have:"
468                     + "\ninitDisplayBounds=" + mInitWindowMetricsBounds
469                     + "\nlastDisplayBounds=" + mLastWindowMetricsBounds
470                     + "\nThe callback has been triggered for " + mCallbackCount + " times.";
471             assertNotEquals(errorMessage, mInitWindowMetricsBounds, mLastWindowMetricsBounds);
472         }
473 
474 
475         /**
476          * Similar to {@link #waitAndAssertDimensionsOffset}, but only verify display bounds.
477          */
waitAndAssertDisplayOffset(int expectedOffset, long startTime)478         void waitAndAssertDisplayOffset(int expectedOffset, long startTime) {
479             waitForLastCallbackTimeout(startTime);
480             assertCallbackTriggered();
481 
482             final String errorMessage = mCallbackName
483                     + ": expect the offset from last display bounds right/bottom to be "
484                     + expectedOffset + ", but have:"
485                     + "\ninitDisplayBounds=" + mInitWindowMetricsBounds
486                     + "\nlastDisplayBounds=" + mLastWindowMetricsBounds
487                     + "\nThe callback has been triggered for " + mCallbackCount + " times.";
488             mInitWindowMetricsBounds.right += expectedOffset;
489             mInitWindowMetricsBounds.bottom += expectedOffset;
490             assertEquals(errorMessage, mInitWindowMetricsBounds, mLastWindowMetricsBounds);
491         }
492 
493         /**
494          * Waits until there is enough time from the last callback. This is to ensure that there is
495          * no unexpected following callbacks with different config.
496          */
waitForLastCallbackTimeout(long startTime)497         private void waitForLastCallbackTimeout(long startTime) {
498             long curTime = SystemClock.elapsedRealtime();
499             if (curTime - CALLBACK_TIMEOUT_MAX_WAITING_TIME_MS >= startTime) {
500                 // No need to wait in case we have waited long enough in other Trackers.
501                 return;
502             }
503 
504             for (int i = 0; i < CALLBACK_TIMEOUT_MAX_RETRY; i++) {
505                 curTime = SystemClock.elapsedRealtime();
506                 if (mCallbackCount > 0
507                         && curTime - CALLBACK_TIMEOUT_MS >= mLastCallbackSystemTime) {
508                     return;
509                 }
510                 Log.i(TAG, "*** Waiting for last callback " + mCallbackName + " IDLE retry=" + i);
511                 SystemClock.sleep(CALLBACK_TIMEOUT_MS);
512             }
513         }
514 
assertCallbackTriggered()515         private void assertCallbackTriggered() {
516             assertNotEquals(mCallbackName + ": callback has never been triggered",
517                     0, mCallbackCount);
518             assertTrue(mCallbackName + ": the last callback didn't wait enough time before timeout",
519                     SystemClock.elapsedRealtime() - CALLBACK_TIMEOUT_MS >= mLastCallbackSystemTime);
520         }
521 
getDisplayRotation()522         private int getDisplayRotation() {
523             return mDm.getDisplay(DEFAULT_DISPLAY).getRotation();
524         }
525 
getActivityRotation()526         private int getActivityRotation() {
527             return mActivity.getResources()
528                     .getConfiguration().windowConfiguration.getDisplayRotation();
529         }
530 
getApplicationRotation()531         private int getApplicationRotation() {
532             return mActivity.getApplicationContext().getResources()
533                     .getConfiguration().windowConfiguration.getDisplayRotation();
534         }
535 
536         @NonNull
getWindowMetricsBounds()537         private Rect getWindowMetricsBounds() {
538             return mWm.getMaximumWindowMetrics().getBounds();
539         }
540 
541         @NonNull
getActivityBounds()542         private Rect getActivityBounds() {
543             return mActivity.getResources()
544                     .getConfiguration().windowConfiguration.getBounds();
545         }
546 
547         @NonNull
getApplicationBounds()548         private Rect getApplicationBounds() {
549             return mActivity.getApplicationContext().getResources()
550                     .getConfiguration().windowConfiguration.getBounds();
551         }
552     }
553 
554     private static class TestComponentCallbacks implements ComponentCallbacks {
555 
556         @NonNull
557         private final WindowConfigTracker mTracker;
558 
TestComponentCallbacks(@onNull WindowConfigTracker tracker)559         TestComponentCallbacks(@NonNull WindowConfigTracker tracker) {
560             mTracker = tracker;
561         }
562 
563         @Override
onConfigurationChanged(@onNull Configuration newConfig)564         public void onConfigurationChanged(@NonNull Configuration newConfig) {
565             Log.d(TAG, "Application#onConfigurationChanged");
566             mTracker.onWindowConfigChanged();
567         }
568 
569         @Override
onLowMemory()570         public void onLowMemory() {}
571     }
572 
573     private static class TestDisplayListener implements DisplayManager.DisplayListener {
574 
575         @NonNull
576         private final WindowConfigTracker mTracker;
577 
TestDisplayListener(@onNull WindowConfigTracker tracker)578         TestDisplayListener(@NonNull WindowConfigTracker tracker) {
579             mTracker = tracker;
580         }
581 
582         @Override
onDisplayAdded(int displayId)583         public void onDisplayAdded(int displayId) {}
584 
585         @Override
onDisplayRemoved(int displayId)586         public void onDisplayRemoved(int displayId) {}
587 
588         @Override
onDisplayChanged(int displayId)589         public void onDisplayChanged(int displayId) {
590             if (displayId == DEFAULT_DISPLAY) {
591                 // Only test against the default display.
592                 Log.d(TAG, "DisplayListener#onDisplayChanged");
593                 mTracker.onWindowConfigChanged();
594             }
595         }
596     }
597 
598     /** Activity to be used for verifying window state {@link #onConfigurationChanged}. */
599     public static class TestActivity extends FocusableActivity {
600 
601         private WindowConfigTracker mTracker;
602 
603         /** Initializes to track the window state. */
setWindowConfigTracker(@onNull WindowConfigTracker tracker)604         void setWindowConfigTracker(@NonNull WindowConfigTracker tracker) {
605             mTracker = tracker;
606         }
607 
608         @Override
onConfigurationChanged(@onNull Configuration newConfig)609         public void onConfigurationChanged(@NonNull Configuration newConfig) {
610             super.onConfigurationChanged(newConfig);
611             if (mTracker != null) {
612                 Log.d(TAG, "Activity#onConfigurationChanged");
613                 mTracker.onWindowConfigChanged();
614             }
615         }
616     }
617 }
618