[go: nahoru, domu]

blob: 1c67712aabb7876aa20327a63e91c4d5c6ba9954 [file] [log] [blame]
/*
* Copyright 2020 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.activity
import android.content.ComponentCallbacks2
import android.content.Intent
import android.content.res.Configuration
import androidx.core.app.MultiWindowModeChangedInfo
import androidx.core.app.PictureInPictureModeChangedInfo
import androidx.core.util.Consumer
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.testutils.withActivity
import androidx.testutils.withUse
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import leakcanary.DetectLeaksAfterTestSuccess
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class ComponentActivityCallbacksTest {
@get:Rule
val rule = DetectLeaksAfterTestSuccess()
@Test
fun onConfigurationChanged() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
var receivedFontScale = withActivity {
resources.configuration.fontScale
}
val expectedFontScale = receivedFontScale * 2
val listener = Consumer<Configuration> { newConfig ->
receivedFontScale = newConfig.fontScale
}
withActivity {
addOnConfigurationChangedListener(listener)
val newConfig = Configuration(resources.configuration)
newConfig.fontScale *= 2
onConfigurationChanged(newConfig)
}
assertThat(receivedFontScale).isEqualTo(expectedFontScale)
}
}
@Test
fun onConfigurationChangedRemove() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
var receivedFontScale = withActivity {
resources.configuration.fontScale
}
val expectedFontScale = receivedFontScale * 2
val listener = Consumer<Configuration> { newConfig ->
receivedFontScale = newConfig.fontScale
}
withActivity {
addOnConfigurationChangedListener(listener)
val newConfig = Configuration(resources.configuration)
newConfig.fontScale *= 2
onConfigurationChanged(newConfig)
}
assertThat(receivedFontScale).isEqualTo(expectedFontScale)
withActivity {
removeOnConfigurationChangedListener(listener)
val newConfig = Configuration(resources.configuration)
newConfig.fontScale *= 2
onConfigurationChanged(newConfig)
}
assertThat(receivedFontScale).isEqualTo(expectedFontScale)
}
}
@Test
fun onConfigurationChangedReentrant() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
val activity = withActivity { this }
var receivedFontScale = withActivity {
resources.configuration.fontScale
}
val expectedFontScale = receivedFontScale * 2
val listener = object : Consumer<Configuration> {
override fun accept(value: Configuration) {
receivedFontScale = value.fontScale
activity.removeOnConfigurationChangedListener(this)
}
}
withActivity {
addOnConfigurationChangedListener(listener)
// Add a second listener to force a ConcurrentModificationException
// if not properly handled by ComponentActivity
addOnConfigurationChangedListener { }
val newConfig = Configuration(resources.configuration)
newConfig.fontScale *= 2
onConfigurationChanged(newConfig)
val secondConfig = Configuration(newConfig)
secondConfig.fontScale *= 2
onConfigurationChanged(secondConfig)
}
// Only the first trim level should be received
assertThat(receivedFontScale).isEqualTo(expectedFontScale)
}
}
@Test
fun onTrimMemory() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
var receivedLevel = -1
val listener = Consumer<Int> { level ->
receivedLevel = level
}
withActivity {
addOnTrimMemoryListener(listener)
onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
}
assertThat(receivedLevel).isEqualTo(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
}
}
@Test
fun onTrimMemoryRemove() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
var receivedLevel = -1
val listener = Consumer<Int> { level ->
receivedLevel = level
}
withActivity {
addOnTrimMemoryListener(listener)
onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
}
assertThat(receivedLevel).isEqualTo(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
withActivity {
removeOnTrimMemoryListener(listener)
onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
}
// Should still be MODERATE and not TRIM_MEMORY_COMPLETE
assertThat(receivedLevel).isEqualTo(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
}
}
@Test
fun onTrimMemoryRemoveReentrant() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
val activity = withActivity { this }
var receivedLevel = -1
val listener = object : Consumer<Int> {
override fun accept(value: Int) {
receivedLevel = value
activity.removeOnTrimMemoryListener(this)
}
}
withActivity {
addOnTrimMemoryListener(listener)
// Add a second listener to force a ConcurrentModificationException
// if not properly handled by ComponentActivity
addOnTrimMemoryListener { }
onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
}
// Only the first trim level should be received
assertThat(receivedLevel).isEqualTo(ComponentCallbacks2.TRIM_MEMORY_MODERATE)
}
}
@Test
fun onNewIntent() {
withUse(ActivityScenario.launch(SingleTopActivity::class.java)) {
val receivedIntents = mutableListOf<Intent>()
val listener = Consumer<Intent> { intent ->
receivedIntents += intent
}
withActivity {
addOnNewIntentListener(listener)
onNewIntent(Intent(this, SingleTopActivity::class.java).apply {
putExtra("newExtra", 5)
})
}
assertWithMessage("Should have received one intent")
.that(receivedIntents)
.hasSize(1)
val receivedIntent = receivedIntents.first()
assertThat(receivedIntent.getIntExtra("newExtra", -1))
.isEqualTo(5)
}
}
@Test
fun onNewIntentRemove() {
withUse(ActivityScenario.launch(SingleTopActivity::class.java)) {
val receivedIntents = mutableListOf<Intent>()
val listener = Consumer<Intent> { intent ->
receivedIntents += intent
}
withActivity {
addOnNewIntentListener(listener)
onNewIntent(Intent(this, SingleTopActivity::class.java).apply {
putExtra("newExtra", 5)
})
}
assertWithMessage("Should have received one intent")
.that(receivedIntents)
.hasSize(1)
val receivedIntent = receivedIntents.first()
assertThat(receivedIntent.getIntExtra("newExtra", -1))
.isEqualTo(5)
withActivity {
removeOnNewIntentListener(listener)
onNewIntent(Intent(this, SingleTopActivity::class.java).apply {
putExtra("newExtra", 5)
})
}
assertWithMessage("Should have received only one intent")
.that(receivedIntents)
.hasSize(1)
}
}
@Test
fun onNewIntentReentrant() {
withUse(ActivityScenario.launch(SingleTopActivity::class.java)) {
val activity = withActivity { this }
val receivedIntents = mutableListOf<Intent>()
val listener = object : Consumer<Intent> {
override fun accept(value: Intent) {
receivedIntents += value
activity.removeOnNewIntentListener(this)
}
}
withActivity {
addOnNewIntentListener(listener)
// Add a second listener to force a ConcurrentModificationException
// if not properly handled by ComponentActivity
addOnNewIntentListener { }
onNewIntent(Intent(this, SingleTopActivity::class.java).apply {
putExtra("newExtra", 5)
})
onNewIntent(Intent(this, SingleTopActivity::class.java).apply {
putExtra("newExtra", 10)
})
}
// Only the first Intent should be received
assertWithMessage("Should have received only one intent")
.that(receivedIntents)
.hasSize(1)
val receivedIntent = receivedIntents.first()
assertThat(receivedIntent.getIntExtra("newExtra", -1))
.isEqualTo(5)
}
}
@Suppress("DEPRECATION")
@Test
fun onMultiWindowModeChanged() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
lateinit var receivedInfo: MultiWindowModeChangedInfo
val listener = Consumer<MultiWindowModeChangedInfo> { info ->
receivedInfo = info
}
withActivity {
addOnMultiWindowModeChangedListener(listener)
onMultiWindowModeChanged(true)
}
assertThat(receivedInfo.isInMultiWindowMode).isTrue()
}
}
@SdkSuppress(minSdkVersion = 26)
@Test
fun onMultiWindowModeChangedWithConfig() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
lateinit var receivedInfo: MultiWindowModeChangedInfo
var dispatchCount = 0
val listener = Consumer<MultiWindowModeChangedInfo> { info ->
receivedInfo = info
dispatchCount++
}
lateinit var newConfig: Configuration
withActivity {
addOnMultiWindowModeChangedListener(listener)
newConfig = Configuration(resources.configuration)
onMultiWindowModeChanged(true, newConfig)
}
assertThat(receivedInfo.isInMultiWindowMode).isTrue()
assertThat(receivedInfo.newConfig).isSameInstanceAs(newConfig)
assertThat(dispatchCount).isEqualTo(1)
}
}
@Suppress("DEPRECATION")
@Test
fun onMultiWindowModeChangedRemove() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
lateinit var receivedInfo: MultiWindowModeChangedInfo
val listener = Consumer<MultiWindowModeChangedInfo> { info ->
receivedInfo = info
}
withActivity {
addOnMultiWindowModeChangedListener(listener)
onMultiWindowModeChanged(true)
}
assertThat(receivedInfo.isInMultiWindowMode).isTrue()
withActivity {
removeOnMultiWindowModeChangedListener(listener)
onMultiWindowModeChanged(false)
}
// Should still be true and not false
assertThat(receivedInfo.isInMultiWindowMode).isTrue()
}
}
@Suppress("DEPRECATION")
@Test
fun onMultiWindowModeChangedRemoveReentrant() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
val activity = withActivity { this }
lateinit var receivedInfo: MultiWindowModeChangedInfo
val listener = object : Consumer<MultiWindowModeChangedInfo> {
override fun accept(value: MultiWindowModeChangedInfo) {
receivedInfo = value
activity.removeOnMultiWindowModeChangedListener(this)
}
}
withActivity {
addOnMultiWindowModeChangedListener(listener)
// Add a second listener to force a ConcurrentModificationException
// if not properly handled by ComponentActivity
addOnMultiWindowModeChangedListener { }
onMultiWindowModeChanged(true)
onMultiWindowModeChanged(false)
}
// Only the first multi-window mode change should be received
assertThat(receivedInfo.isInMultiWindowMode).isTrue()
}
}
@Suppress("DEPRECATION")
@Test
fun onPictureInPictureModeChanged() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
lateinit var receivedInfo: PictureInPictureModeChangedInfo
val listener = Consumer<PictureInPictureModeChangedInfo> { info ->
receivedInfo = info
}
withActivity {
addOnPictureInPictureModeChangedListener(listener)
onPictureInPictureModeChanged(true)
}
assertThat(receivedInfo.isInPictureInPictureMode).isTrue()
}
}
@SdkSuppress(minSdkVersion = 26)
@Test
fun onPictureInPictureModeChangedWithConfig() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
lateinit var receivedInfo: PictureInPictureModeChangedInfo
var dispatchCount = 0
val listener = Consumer<PictureInPictureModeChangedInfo> { info ->
receivedInfo = info
dispatchCount++
}
lateinit var newConfig: Configuration
withActivity {
addOnPictureInPictureModeChangedListener(listener)
newConfig = Configuration(resources.configuration)
onPictureInPictureModeChanged(true, newConfig)
}
assertThat(receivedInfo.isInPictureInPictureMode).isTrue()
assertThat(receivedInfo.newConfig).isSameInstanceAs(newConfig)
assertThat(dispatchCount).isEqualTo(1)
}
}
@Suppress("DEPRECATION")
@Test
fun onPictureInPictureModeChangedRemove() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
lateinit var receivedInfo: PictureInPictureModeChangedInfo
val listener = Consumer<PictureInPictureModeChangedInfo> { info ->
receivedInfo = info
}
withActivity {
addOnPictureInPictureModeChangedListener(listener)
onPictureInPictureModeChanged(true)
}
assertThat(receivedInfo.isInPictureInPictureMode).isTrue()
withActivity {
removeOnPictureInPictureModeChangedListener(listener)
onPictureInPictureModeChanged(false)
}
// Should still be true and not false
assertThat(receivedInfo.isInPictureInPictureMode).isTrue()
}
}
@Suppress("DEPRECATION")
@Test
fun onPictureInPictureModeChangedRemoveReentrant() {
withUse(ActivityScenario.launch(ComponentActivity::class.java)) {
val activity = withActivity { this }
lateinit var receivedInfo: PictureInPictureModeChangedInfo
val listener = object : Consumer<PictureInPictureModeChangedInfo> {
override fun accept(value: PictureInPictureModeChangedInfo) {
receivedInfo = value
activity.removeOnPictureInPictureModeChangedListener(this)
}
}
withActivity {
addOnPictureInPictureModeChangedListener(listener)
// Add a second listener to force a ConcurrentModificationException
// if not properly handled by ComponentActivity
addOnPictureInPictureModeChangedListener { }
onPictureInPictureModeChanged(true)
onPictureInPictureModeChanged(false)
}
// Only the first picture-in-picture mode change should be received
assertThat(receivedInfo.isInPictureInPictureMode).isTrue()
}
}
@Test
fun onUserLeaveHint() {
withUse(ActivityScenario.launch(OnUserLeaveHintActivity::class.java)) {
var receivedOnUserLeaveHint = false
val listener = Runnable { receivedOnUserLeaveHint = true }
withActivity {
addOnUserLeaveHintListener(listener)
onUserLeaveHint()
}
assertThat(receivedOnUserLeaveHint).isEqualTo(true)
}
}
@Test
fun onUserLeaveHintRemove() {
withUse(ActivityScenario.launch(OnUserLeaveHintActivity::class.java)) {
var receivedOnUserLeaveHintCount = 0
val listener = Runnable { receivedOnUserLeaveHintCount++ }
withActivity {
addOnUserLeaveHintListener(listener)
onUserLeaveHint()
}
assertThat(receivedOnUserLeaveHintCount).isEqualTo(1)
withActivity {
removeOnUserLeaveHintListener(listener)
onUserLeaveHint()
}
// should still be 1
assertThat(receivedOnUserLeaveHintCount).isEqualTo(1)
}
}
}
class SingleTopActivity : ComponentActivity() {
public override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
}
}
class OnUserLeaveHintActivity : ComponentActivity() {
public override fun onUserLeaveHint() {
super.onUserLeaveHint()
}
}