blob: c0139a5ec0879e927e86ea076a39f6c9b0998e72 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.appcompat.app
import android.content.Context
import android.content.res.Configuration
import android.location.LocationManager
import android.os.Build
import android.webkit.WebView
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import androidx.appcompat.test.R
import androidx.appcompat.testutils.NightModeActivityTestRule
import androidx.appcompat.testutils.NightModeUtils.NightSetMode
import androidx.appcompat.testutils.NightModeUtils.assertConfigurationNightModeEquals
import androidx.appcompat.testutils.NightModeUtils.isSystemNightThemeEnabled
import androidx.appcompat.testutils.NightModeUtils.setNightModeAndWait
import androidx.appcompat.testutils.NightModeUtils.setNightModeAndWaitForRecreate
import androidx.appcompat.testutils.TestUtilsMatchers.isBackground
import androidx.core.content.ContextCompat
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.LifecycleOwnerUtils.waitForRecreation
import androidx.testutils.waitForExecution
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.concurrent.CountDownLatch
@Suppress("DEPRECATION")
@LargeTest
@RunWith(Parameterized::class)
class NightModeTestCase(private val setMode: NightSetMode) {
@get:Rule
val rule = NightModeActivityTestRule(NightModeActivity::class.java)
@Test
fun testSwitchingYesDoesNotAffectApplication() {
// This test is only useful when the dark system theme is not enabled
if (!isSystemNightThemeEnabled(rule.activity)) {
assertConfigurationNightModeEquals(
Configuration.UI_MODE_NIGHT_NO,
rule.activity.applicationContext.resources.configuration
)
// Force the night mode to be yes (aka night mode)
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_YES, setMode)
assertConfigurationNightModeEquals(
Configuration.UI_MODE_NIGHT_NO,
rule.activity.applicationContext.resources.configuration
)
}
}
@Test
fun testSwitchingYesToFollowSystem() {
// This test is only useful when the dark system theme is enabled
if (!isSystemNightThemeEnabled(rule.activity)) {
// Now force the night mode to be YES, so we're in dark theme
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_YES, setMode)
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT)))
// Now force the local night mode to be FOLLOW_SYSTEM (which we know is dark)
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_FOLLOW_SYSTEM, setMode)
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)))
}
}
@Test
fun testSwitchingNoToFollowSystem() {
// This test is only useful when the dark system theme is enabled
if (isSystemNightThemeEnabled(rule.activity)) {
// Now force the night mode to be NO, so we're in light theme
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_NO, setMode)
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)))
// Now force the night mode to be FOLLOW_SYSTEM (which we know is dark)
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_FOLLOW_SYSTEM, setMode)
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT)))
}
}
@Test
fun testColorConvertedDrawableChangesWithNightMode() {
val activity = rule.activity
val dayColor = ContextCompat.getColor(activity, R.color.color_sky_day)
val nightColor = ContextCompat.getColor(activity, R.color.color_sky_night)
// Loop through and switching from day to night and vice-versa multiple times. It needs
// to be looped since the issue is with drawable caching, therefore we need to prime the
// cache for the issue to happen
for (i in 0 until 5) {
// First force it to be night mode and assert the color
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_YES, setMode)
onView(withId(R.id.view_background)).check(matches(isBackground(nightColor)))
// Now force the local night mode to be no (aka day mode) and assert the color
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_NO, setMode)
onView(withId(R.id.view_background)).check(matches(isBackground(dayColor)))
}
}
@Test
fun testNightModeAutoTimeRecreatesOnTimeChange() {
// Create a fake TwilightManager and set it as the app instance
val twilightManager = FakeTwilightManager(rule.activity)
TwilightManager.setInstance(twilightManager)
// Verify that we're currently in day mode
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)))
// Set MODE_NIGHT_AUTO so that we will change to night mode automatically
setNightModeAndWait(rule, AppCompatDelegate.MODE_NIGHT_AUTO_TIME, setMode)
val newDelegate = rule.activity.delegate as AppCompatDelegateImpl
// Update the fake twilight manager to be in night and trigger a fake 'time' change
rule.runOnUiThread {
twilightManager.isNightForTest = true
newDelegate.autoTimeNightModeManager.onChange()
}
// Now wait until the Activity is destroyed (thus recreated)
waitForRecreation(rule)
// Check that the text has changed, signifying that night resources are being used
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT)))
}
@Test
fun testNightModeAutoTimeRecreatesOnResume() {
// Create a fake TwilightManager and set it as the app instance
val twilightManager = FakeTwilightManager(rule.activity)
TwilightManager.setInstance(twilightManager)
// Set MODE_NIGHT_AUTO_TIME so that we will change to night mode automatically
setNightModeAndWait(rule, AppCompatDelegate.MODE_NIGHT_AUTO_TIME, setMode)
// Verify that we're currently in day mode
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)))
val resumeCompleteLatch = CountDownLatch(1)
rule.runOnUiThread {
val activity = rule.activity
val instrumentation = InstrumentationRegistry.getInstrumentation()
// Now fool the Activity into thinking that it has gone into the background
instrumentation.callActivityOnPause(activity)
instrumentation.callActivityOnStop(activity)
// Now update the twilight manager while the Activity is in the 'background'
twilightManager.isNightForTest = true
// Now tell the Activity that it has gone into the foreground again
instrumentation.callActivityOnStart(activity)
instrumentation.callActivityOnResume(activity)
resumeCompleteLatch.countDown()
}
resumeCompleteLatch.await()
// finally check that the text has changed, signifying that night resources are being used
onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT)))
}
@Test
fun testOnNightModeChangedCalled() {
val activity = rule.activity
// Set local night mode to YES
setNightModeAndWait(rule, MODE_NIGHT_YES, setMode)
// Assert that the Activity received a new value
assertEquals(MODE_NIGHT_YES, activity.lastNightModeAndReset)
}
@Test
fun testDialogDoesNotOverrideActivityConfiguration() {
// Set Activity local night mode to YES
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_YES, setMode)
// Assert that the uiMode is as expected
assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES, rule.activity)
// Now show a AppCompatDialog
lateinit var dialog: AppCompatDialog
rule.runOnUiThread {
dialog = AppCompatDialog(rule.activity)
dialog.show()
}
rule.waitForExecution()
// Now dismiss the dialog
rule.runOnUiThread { dialog.dismiss() }
// Assert that the uiMode is unchanged
assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES, rule.activity)
}
@Test
fun testLoadingWebViewMaintainsConfiguration() {
// Assert that the context has a non-night themed configuration
assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_NO, rule.activity)
// Set night mode and wait for the new Activity
setNightModeAndWaitForRecreate(rule, MODE_NIGHT_YES, setMode)
// Assert that the context still has a night themed configuration
assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES, rule.activity)
// Now load a WebView into the Activity
rule.runOnUiThread { WebView(rule.activity) }
// Now assert that the context still has a night themed configuration
assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES, rule.activity)
}
@Test
@SdkSuppress(minSdkVersion = 17)
fun testDialogCleansUpAutoMode() = rule.runOnUiThread {
val dialog = AppCompatDialog(rule.activity)
val delegate = dialog.delegate as AppCompatDelegateImpl
// Set the local night mode of the Dialog to be an AUTO mode
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_AUTO_TIME
// Now show and dismiss the dialog
dialog.show()
dialog.dismiss()
// Assert that the auto manager is destroyed (not listening)
assertFalse(delegate.autoTimeNightModeManager.isListening)
}
@Test
fun testOnConfigurationChangeNotCalled() {
var activity = rule.activity
// Set local night mode to YES
setNightModeAndWait(rule, MODE_NIGHT_YES, setMode)
// Assert that onConfigurationChange was not called on the original activity
assertNull(activity.lastConfigurationChangeAndClear)
activity = rule.activity
// Set local night mode back to NO
setNightModeAndWait(rule, MODE_NIGHT_NO, setMode)
// Assert that onConfigurationChange was not called
assertNull(activity.lastConfigurationChangeAndClear)
}
private class FakeTwilightManager(context: Context) : TwilightManager(
context,
ContextCompat.getSystemService(context, LocationManager::class.java)!!
) {
var isNightForTest: Boolean = false
override fun isNight(): Boolean {
return isNightForTest
}
}
companion object {
private const val STRING_DAY = "DAY"
private const val STRING_NIGHT = "NIGHT"
@Parameterized.Parameters
@JvmStatic
fun data() = if (Build.VERSION.SDK_INT >= 17) {
listOf(NightSetMode.DEFAULT, NightSetMode.LOCAL)
} else {
listOf(NightSetMode.DEFAULT)
}
}
}