Merge changes I4e2c4584,I81919822,Ic3968b89 into tm-qpr-dev

* changes:
  Write tests for all of ConfigurationControllerImpl.
  [Privacy Chip] Minor updates to StatusBarContentInsetsProviderTest
  Update ConfigurationController to not store maxBounds by reference.
This commit is contained in:
Caitlin Shkuratov
2022-11-15 22:26:18 +00:00
committed by Android (Google) Code Review
3 changed files with 397 additions and 32 deletions

View File

@@ -33,7 +33,7 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
private val lastConfig = Configuration() private val lastConfig = Configuration()
private var density: Int = 0 private var density: Int = 0
private var smallestScreenWidth: Int = 0 private var smallestScreenWidth: Int = 0
private var maxBounds: Rect? = null private var maxBounds = Rect()
private var fontScale: Float = 0.toFloat() private var fontScale: Float = 0.toFloat()
private val inCarMode: Boolean private val inCarMode: Boolean
private var uiMode: Int = 0 private var uiMode: Int = 0
@@ -47,6 +47,7 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
fontScale = currentConfig.fontScale fontScale = currentConfig.fontScale
density = currentConfig.densityDpi density = currentConfig.densityDpi
smallestScreenWidth = currentConfig.smallestScreenWidthDp smallestScreenWidth = currentConfig.smallestScreenWidthDp
maxBounds.set(currentConfig.windowConfiguration.maxBounds)
inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK == inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
Configuration.UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR
uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
val maxBounds = newConfig.windowConfiguration.maxBounds val maxBounds = newConfig.windowConfiguration.maxBounds
if (maxBounds != this.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) }) { listeners.filterForEach({ this.listeners.contains(it) }) {
it.onMaxBoundsChanged() it.onMaxBoundsChanged()
} }

View File

@@ -14,23 +14,37 @@
package com.android.systemui.statusbar.phone package com.android.systemui.statusbar.phone
import androidx.test.filters.SmallTest import android.content.res.Configuration
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 android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener 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.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import java.util.Locale
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@SmallTest @SmallTest
class ConfigurationControllerImplTest : SysuiTestCase() { class ConfigurationControllerImplTest : SysuiTestCase() {
private val mConfigurationController = private lateinit var mConfigurationController: ConfigurationControllerImpl
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
@Before
fun setUp() {
mConfigurationController = ConfigurationControllerImpl(mContext)
}
@Test @Test
fun testThemeChange() { fun testThemeChange() {
@@ -57,4 +71,303 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
verify(listener).onThemeChanged() verify(listener).onThemeChanged()
verify(listener2, never()).onThemeChanged() 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 = createAndAddListener()
// 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.maxBoundsChanged).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 = 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.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
}
}
} }

View File

@@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Rect import android.graphics.Rect
import android.test.suitebuilder.annotation.SmallTest
import android.view.Display import android.view.Display
import android.view.DisplayCutout import android.view.DisplayCutout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val provider = StatusBarContentInsetsProvider(contextMock, configurationController, val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java)) mock(DumpManager::class.java))
givenDisplay( configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
screenBounds = Rect(0, 0, 1080, 2160),
displayUniqueId = "1"
)
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
givenDisplay(
screenBounds = Rect(0, 0, 800, 600), configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
displayUniqueId = "2"
)
configurationController.onConfigurationChanged(configuration)
// WHEN: get insets on the second display // WHEN: get insets on the second display
val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
// get insets and switch back // get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController, val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java)) mock(DumpManager::class.java))
givenDisplay(
screenBounds = Rect(0, 0, 1080, 2160), configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
displayUniqueId = "1"
)
val firstDisplayInsetsFirstCall = provider val firstDisplayInsetsFirstCall = provider
.getStatusBarContentAreaForRotation(ROTATION_NONE) .getStatusBarContentAreaForRotation(ROTATION_NONE)
givenDisplay(
screenBounds = Rect(0, 0, 800, 600), configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
displayUniqueId = "2"
)
configurationController.onConfigurationChanged(configuration)
provider.getStatusBarContentAreaForRotation(ROTATION_NONE) provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
givenDisplay(
screenBounds = Rect(0, 0, 1080, 2160), configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
displayUniqueId = "1"
)
configurationController.onConfigurationChanged(configuration)
// WHEN: get insets on the first display again // WHEN: get insets on the first display again
val firstDisplayInsetsSecondCall = provider val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall) assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
} }
private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) { // Regression test for b/245799099
`when`(display.uniqueId).thenReturn(displayUniqueId) @Test
configuration.windowConfiguration.maxBounds = screenBounds 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()
}
@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( private fun assertRects(