From be5a6e5c5107a06c9734f42fe9f033cb3db99b1e Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 14 Nov 2022 18:53:20 +0000 Subject: [PATCH 1/3] Update ConfigurationController to not store maxBounds by reference. See bug for more information, but tl;dr is: Since maxBounds was directly referencing the configuration's bounds, it got updated before `onConfigurationChanged` was triggered. This meant listeners never got the `onMaxBoundsChanged` callback, so `PrivacyViewDotController` never updated its bounds and has the incorrect bounds. Bug: 245799099 Bug: 256754780 Bug: 259105114 Test: manual: Verify privacy dot shows up correctly in all displays Test: atest ConfigurationControllerImplTest Change-Id: Ic3968b89a240f28630eff756ca0a7eaacbf5dee2 --- .../phone/ConfigurationControllerImpl.kt | 8 ++- .../phone/ConfigurationControllerImplTest.kt | 53 ++++++++++++++++++- .../StatusBarContentInsetsProviderTest.kt | 25 +++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 34cd1ceeffc71..110dc90b62918 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -33,7 +33,7 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config private val lastConfig = Configuration() private var density: Int = 0 private var smallestScreenWidth: Int = 0 - private var maxBounds: Rect? = null + private var maxBounds = Rect() private var fontScale: Float = 0.toFloat() private val inCarMode: Boolean private var uiMode: Int = 0 @@ -92,7 +92,11 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config val maxBounds = newConfig.windowConfiguration.maxBounds if (maxBounds != this.maxBounds) { - this.maxBounds = maxBounds + // Update our internal rect to have the same bounds, instead of using + // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds` + // would be a direct reference to windowConfiguration.maxBounds, so the if statement + // above would always fail. See b/245799099 for more information. + this.maxBounds.set(maxBounds) listeners.filterForEach({ this.listeners.contains(it) }) { it.onMaxBoundsChanged() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index fee3ccb217922..b1e62e19dd3bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -14,10 +14,12 @@ package com.android.systemui.statusbar.phone +import android.content.res.Configuration import androidx.test.filters.SmallTest import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener +import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doAnswer @@ -29,8 +31,7 @@ import org.mockito.Mockito.verify @SmallTest class ConfigurationControllerImplTest : SysuiTestCase() { - private val mConfigurationController = - com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext) + private val mConfigurationController = ConfigurationControllerImpl(mContext) @Test fun testThemeChange() { @@ -57,4 +58,52 @@ class ConfigurationControllerImplTest : SysuiTestCase() { verify(listener).onThemeChanged() verify(listener2, never()).onThemeChanged() } + + @Test + fun maxBoundsChange_newConfigObject_listenerNotified() { + val config = mContext.resources.configuration + config.windowConfiguration.setMaxBounds(0, 0, 200, 200) + mConfigurationController.onConfigurationChanged(config) + + val listener = object : ConfigurationListener { + var triggered: Boolean = false + + override fun onMaxBoundsChanged() { + triggered = true + } + } + mConfigurationController.addCallback(listener) + + // WHEN a new configuration object with new bounds is sent + val newConfig = Configuration() + newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100) + mConfigurationController.onConfigurationChanged(newConfig) + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() + } + + // Regression test for b/245799099 + @Test + fun maxBoundsChange_sameObject_listenerNotified() { + val config = mContext.resources.configuration + config.windowConfiguration.setMaxBounds(0, 0, 200, 200) + mConfigurationController.onConfigurationChanged(config) + + val listener = object : ConfigurationListener { + var triggered: Boolean = false + + override fun onMaxBoundsChanged() { + triggered = true + } + } + mConfigurationController.addCallback(listener) + + // WHEN the existing config is updated with new bounds + config.windowConfiguration.setMaxBounds(0, 0, 100, 100) + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index e86676b81f8eb..e32260d4c42b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -513,6 +513,31 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall) } + // Regression test for b/245799099 + @Test + fun onMaxBoundsChanged_listenerNotified() { + // Start out with an existing configuration with bounds + configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100) + configurationController.onConfigurationChanged(configuration) + val provider = StatusBarContentInsetsProvider(contextMock, configurationController, + mock(DumpManager::class.java)) + val listener = object : StatusBarContentInsetsChangedListener { + var triggered = false + + override fun onStatusBarContentInsetsChanged() { + triggered = true + } + } + provider.addCallback(listener) + + // WHEN the config is updated with new bounds + configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789) + configurationController.onConfigurationChanged(configuration) + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() + } + private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) { `when`(display.uniqueId).thenReturn(displayUniqueId) configuration.windowConfiguration.maxBounds = screenBounds From 0f2a227b396a6263c91a3d6d3bc1df7bf8bf9368 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 14 Nov 2022 19:41:07 +0000 Subject: [PATCH 2/3] [Privacy Chip] Minor updates to StatusBarContentInsetsProviderTest Updates: - A previous CL updated the provider to not care about `context.display.uniqueId`, so this updates the test to not set that ID either. - Remove `ConfigurationController.onConfigurationChanged` calls, since they weren't needed (the provider just uses the context directly) - Add additional listener tests Bug: 212924195 Bug: 245799099 Test: atest StatusBarContentInsetsProviderTest Change-Id: I8191982299f26c01b2a0c92bdfd8e2f5551c7568 --- .../StatusBarContentInsetsProviderTest.kt | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index e32260d4c42b3..1759fb794bd6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone import android.content.Context import android.content.res.Configuration import android.graphics.Rect -import android.test.suitebuilder.annotation.SmallTest import android.view.Display import android.view.DisplayCutout +import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.policy.ConfigurationController @@ -463,16 +463,10 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val provider = StatusBarContentInsetsProvider(contextMock, configurationController, mock(DumpManager::class.java)) - givenDisplay( - screenBounds = Rect(0, 0, 1080, 2160), - displayUniqueId = "1" - ) + configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160) val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) - givenDisplay( - screenBounds = Rect(0, 0, 800, 600), - displayUniqueId = "2" - ) - configurationController.onConfigurationChanged(configuration) + + configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600) // WHEN: get insets on the second display val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) @@ -487,23 +481,15 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // get insets and switch back val provider = StatusBarContentInsetsProvider(contextMock, configurationController, mock(DumpManager::class.java)) - givenDisplay( - screenBounds = Rect(0, 0, 1080, 2160), - displayUniqueId = "1" - ) + + configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160) val firstDisplayInsetsFirstCall = provider .getStatusBarContentAreaForRotation(ROTATION_NONE) - givenDisplay( - screenBounds = Rect(0, 0, 800, 600), - displayUniqueId = "2" - ) - configurationController.onConfigurationChanged(configuration) + + configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600) provider.getStatusBarContentAreaForRotation(ROTATION_NONE) - givenDisplay( - screenBounds = Rect(0, 0, 1080, 2160), - displayUniqueId = "1" - ) - configurationController.onConfigurationChanged(configuration) + + configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160) // WHEN: get insets on the first display again val firstDisplayInsetsSecondCall = provider @@ -538,9 +524,45 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { assertThat(listener.triggered).isTrue() } - private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) { - `when`(display.uniqueId).thenReturn(displayUniqueId) - configuration.windowConfiguration.maxBounds = screenBounds + @Test + fun onDensityOrFontScaleChanged_listenerNotified() { + configuration.densityDpi = 12 + val provider = StatusBarContentInsetsProvider(contextMock, configurationController, + mock(DumpManager::class.java)) + val listener = object : StatusBarContentInsetsChangedListener { + var triggered = false + + override fun onStatusBarContentInsetsChanged() { + triggered = true + } + } + provider.addCallback(listener) + + // WHEN the config is updated + configuration.densityDpi = 20 + configurationController.onConfigurationChanged(configuration) + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() + } + + @Test + fun onThemeChanged_listenerNotified() { + val provider = StatusBarContentInsetsProvider(contextMock, configurationController, + mock(DumpManager::class.java)) + val listener = object : StatusBarContentInsetsChangedListener { + var triggered = false + + override fun onStatusBarContentInsetsChanged() { + triggered = true + } + } + provider.addCallback(listener) + + configurationController.notifyThemeChanged() + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() } private fun assertRects( From 714485160cd4c97d0f97f8eb063649a1f9badcf8 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 14 Nov 2022 21:25:56 +0000 Subject: [PATCH 3/3] Write tests for all of ConfigurationControllerImpl. Fixes: 259105114 Test: atest ConfigurationControllerImplTest Test: verify privacy indicator still works on all displays (b/245799099) Change-Id: I4e2c4584c3f18cf2157afc6a38434002ba307b65 --- .../phone/ConfigurationControllerImpl.kt | 1 + .../phone/ConfigurationControllerImplTest.kt | 304 ++++++++++++++++-- 2 files changed, 285 insertions(+), 20 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 110dc90b62918..7dcdc0bdb3832 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -47,6 +47,7 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config fontScale = currentConfig.fontScale density = currentConfig.densityDpi smallestScreenWidth = currentConfig.smallestScreenWidthDp + maxBounds.set(currentConfig.windowConfiguration.maxBounds) inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK == Configuration.UI_MODE_TYPE_CAR uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index b1e62e19dd3bb..038af8ff53967 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -15,23 +15,36 @@ package com.android.systemui.statusbar.phone import android.content.res.Configuration -import androidx.test.filters.SmallTest +import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR +import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.content.res.Configuration.UI_MODE_TYPE_CAR +import android.os.LocaleList import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doAnswer import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify +import java.util.Locale @RunWith(AndroidTestingRunner::class) @SmallTest class ConfigurationControllerImplTest : SysuiTestCase() { - private val mConfigurationController = ConfigurationControllerImpl(mContext) + private lateinit var mConfigurationController: ConfigurationControllerImpl + + @Before + fun setUp() { + mConfigurationController = ConfigurationControllerImpl(mContext) + } @Test fun testThemeChange() { @@ -59,20 +72,114 @@ class ConfigurationControllerImplTest : SysuiTestCase() { verify(listener2, never()).onThemeChanged() } + @Test + fun configChanged_listenerNotified() { + val config = mContext.resources.configuration + config.densityDpi = 12 + config.smallestScreenWidthDp = 240 + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the config is updated + config.densityDpi = 20 + config.smallestScreenWidthDp = 300 + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.changedConfig?.densityDpi).isEqualTo(20) + assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300) + } + + @Test + fun densityChanged_listenerNotified() { + val config = mContext.resources.configuration + config.densityDpi = 12 + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the density is updated + config.densityDpi = 20 + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.densityOrFontScaleChanged).isTrue() + } + + @Test + fun fontChanged_listenerNotified() { + val config = mContext.resources.configuration + config.fontScale = 1.5f + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the font is updated + config.fontScale = 1.4f + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.densityOrFontScaleChanged).isTrue() + } + + @Test + fun isCarAndUiModeChanged_densityListenerNotified() { + val config = mContext.resources.configuration + config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES + // Re-create the controller since we calculate car mode on creation + mConfigurationController = ConfigurationControllerImpl(mContext) + + val listener = createAndAddListener() + + // WHEN the ui mode is updated + config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.densityOrFontScaleChanged).isTrue() + } + + @Test + fun isNotCarAndUiModeChanged_densityListenerNotNotified() { + val config = mContext.resources.configuration + config.uiMode = UI_MODE_NIGHT_YES + // Re-create the controller since we calculate car mode on creation + mConfigurationController = ConfigurationControllerImpl(mContext) + + val listener = createAndAddListener() + + // WHEN the ui mode is updated + config.uiMode = UI_MODE_NIGHT_NO + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is not notified because it's not car mode + assertThat(listener.densityOrFontScaleChanged).isFalse() + } + + @Test + fun smallestScreenWidthChanged_listenerNotified() { + val config = mContext.resources.configuration + config.smallestScreenWidthDp = 240 + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the width is updated + config.smallestScreenWidthDp = 300 + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.smallestScreenWidthChanged).isTrue() + } + @Test fun maxBoundsChange_newConfigObject_listenerNotified() { val config = mContext.resources.configuration config.windowConfiguration.setMaxBounds(0, 0, 200, 200) mConfigurationController.onConfigurationChanged(config) - val listener = object : ConfigurationListener { - var triggered: Boolean = false - - override fun onMaxBoundsChanged() { - triggered = true - } - } - mConfigurationController.addCallback(listener) + val listener = createAndAddListener() // WHEN a new configuration object with new bounds is sent val newConfig = Configuration() @@ -80,7 +187,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() { mConfigurationController.onConfigurationChanged(newConfig) // THEN the listener is notified - assertThat(listener.triggered).isTrue() + assertThat(listener.maxBoundsChanged).isTrue() } // Regression test for b/245799099 @@ -90,20 +197,177 @@ class ConfigurationControllerImplTest : SysuiTestCase() { config.windowConfiguration.setMaxBounds(0, 0, 200, 200) mConfigurationController.onConfigurationChanged(config) - val listener = object : ConfigurationListener { - var triggered: Boolean = false - - override fun onMaxBoundsChanged() { - triggered = true - } - } - mConfigurationController.addCallback(listener) + val listener = createAndAddListener() // WHEN the existing config is updated with new bounds config.windowConfiguration.setMaxBounds(0, 0, 100, 100) mConfigurationController.onConfigurationChanged(config) // THEN the listener is notified - assertThat(listener.triggered).isTrue() + assertThat(listener.maxBoundsChanged).isTrue() + } + + + @Test + fun localeListChanged_listenerNotified() { + val config = mContext.resources.configuration + config.locales = LocaleList(Locale.CANADA, Locale.GERMANY) + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the locales are updated + config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE) + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.localeListChanged).isTrue() + } + + @Test + fun uiModeChanged_listenerNotified() { + val config = mContext.resources.configuration + config.uiMode = UI_MODE_NIGHT_YES + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the ui mode is updated + config.uiMode = UI_MODE_NIGHT_NO + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.uiModeChanged).isTrue() + } + + @Test + fun layoutDirectionUpdated_listenerNotified() { + val config = mContext.resources.configuration + config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the layout is updated + config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.layoutDirectionChanged).isTrue() + } + + @Test + fun assetPathsUpdated_listenerNotified() { + val config = mContext.resources.configuration + config.assetsSeq = 45 + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN the assets sequence is updated + config.assetsSeq = 46 + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified + assertThat(listener.themeChanged).isTrue() + } + + @Test + fun multipleUpdates_listenerNotifiedOfAll() { + val config = mContext.resources.configuration + config.densityDpi = 14 + config.windowConfiguration.setMaxBounds(0, 0, 2, 2) + config.uiMode = UI_MODE_NIGHT_YES + mConfigurationController.onConfigurationChanged(config) + + val listener = createAndAddListener() + + // WHEN multiple fields are updated + config.densityDpi = 20 + config.windowConfiguration.setMaxBounds(0, 0, 3, 3) + config.uiMode = UI_MODE_NIGHT_NO + mConfigurationController.onConfigurationChanged(config) + + // THEN the listener is notified of all of them + assertThat(listener.densityOrFontScaleChanged).isTrue() + assertThat(listener.maxBoundsChanged).isTrue() + assertThat(listener.uiModeChanged).isTrue() + } + + @Test + fun equivalentConfigObject_listenerNotNotified() { + val config = mContext.resources.configuration + val listener = createAndAddListener() + + // WHEN we update with the new object that has all the same fields + mConfigurationController.onConfigurationChanged(Configuration(config)) + + listener.assertNoMethodsCalled() + } + + private fun createAndAddListener(): TestListener { + val listener = TestListener() + mConfigurationController.addCallback(listener) + // Adding a listener can trigger some callbacks, so we want to reset the values right + // after the listener is added + listener.reset() + return listener + } + + private class TestListener : ConfigurationListener { + var changedConfig: Configuration? = null + var densityOrFontScaleChanged = false + var smallestScreenWidthChanged = false + var maxBoundsChanged = false + var uiModeChanged = false + var themeChanged = false + var localeListChanged = false + var layoutDirectionChanged = false + + override fun onConfigChanged(newConfig: Configuration?) { + changedConfig = newConfig + } + override fun onDensityOrFontScaleChanged() { + densityOrFontScaleChanged = true + } + override fun onSmallestScreenWidthChanged() { + smallestScreenWidthChanged = true + } + override fun onMaxBoundsChanged() { + maxBoundsChanged = true + } + override fun onUiModeChanged() { + uiModeChanged = true + } + override fun onThemeChanged() { + themeChanged = true + } + override fun onLocaleListChanged() { + localeListChanged = true + } + override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) { + layoutDirectionChanged = true + } + + fun assertNoMethodsCalled() { + assertThat(densityOrFontScaleChanged).isFalse() + assertThat(smallestScreenWidthChanged).isFalse() + assertThat(maxBoundsChanged).isFalse() + assertThat(uiModeChanged).isFalse() + assertThat(themeChanged).isFalse() + assertThat(localeListChanged).isFalse() + assertThat(layoutDirectionChanged).isFalse() + } + + fun reset() { + changedConfig = null + densityOrFontScaleChanged = false + smallestScreenWidthChanged = false + maxBoundsChanged = false + uiModeChanged = false + themeChanged = false + localeListChanged = false + layoutDirectionChanged = false + } } }