blob: 0c3d34e686dd58b291b8c7cf67b2cdbf06842ae6 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.content.res.loader.test
import android.app.Activity
import android.app.Instrumentation
import android.app.UiAutomation
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.widget.FrameLayout
import androidx.test.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.Arrays
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.concurrent.FutureTask
import java.util.concurrent.TimeUnit
@RunWith(Parameterized::class)
class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
companion object {
private const val TIMEOUT = 30L
private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun data() = arrayOf(DataType.APK, DataType.ARSC)
}
@field:Parameterized.Parameter(0)
override lateinit var dataType: DataType
@get:Rule
val activityRule: ActivityTestRule<TestActivity> =
ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
// Redirect to the Activity's resources
override val resources: Resources
get() = activityRule.getActivity().resources
private val activity: TestActivity
get() = activityRule.getActivity()
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@Before
@After
fun disableOverlay() {
enableOverlay(OVERLAY_PACKAGE, false)
}
@Test
fun activityRecreate() = verifySameBeforeAndAfter {
val oldActivity = activity
var newActivity: Activity? = null
instrumentation.runOnMainSync { oldActivity.recreate() }
instrumentation.waitForIdleSync()
instrumentation.runOnMainSync {
newActivity = ActivityLifecycleMonitorRegistry.getInstance()
.getActivitiesInStage(Stage.RESUMED)
.single()
}
assertThat(newActivity).isNotNull()
assertThat(newActivity).isNotSameAs(oldActivity)
// Return the new resources to assert on
return@verifySameBeforeAndAfter newActivity!!.resources
}
@Test
fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
val latch = CountDownLatch(1)
val oldConfig = Configuration().apply { setTo(resources.configuration) }
var changedConfig: Configuration? = null
activity.callback = object : TestActivity.Callback {
override fun onConfigurationChanged(newConfig: Configuration) {
changedConfig = newConfig
latch.countDown()
}
}
val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
val newRotation = if (isPortrait) {
UiAutomation.ROTATION_FREEZE_90
} else {
UiAutomation.ROTATION_FREEZE_0
}
instrumentation.uiAutomation.setRotation(newRotation)
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
assertThat(changedConfig).isNotEqualTo(oldConfig)
return@verifySameBeforeAndAfter activity.resources
}
@Test
fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
enableOverlay(OVERLAY_PACKAGE, true)
assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
return@verifySameBeforeAndAfter activity.resources
}
@Test
fun enableOverlayChildContextUnaffected() {
val childContext = activity.createConfigurationContext(Configuration())
val childResources = childContext.resources
val originalValue = childResources.getString(android.R.string.cancel)
assertThat(childResources.getString(R.string.loader_path_change_test))
.isEqualTo("Not overlaid")
verifySameBeforeAndAfter {
enableOverlay(OVERLAY_PACKAGE, true)
return@verifySameBeforeAndAfter activity.resources
}
// Loader not applied, but overlay change propagated
assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
assertThat(childResources.getString(R.string.loader_path_change_test))
.isEqualTo("Overlaid")
}
// All these tests assert for the exact same loaders/values, so extract that logic out
private fun verifySameBeforeAndAfter(block: () -> Resources) {
fun Resources.resource() = this.getString(android.R.string.cancel)
fun Resources.asset() = this.assets.open("Asset.txt").reader().readText()
val originalResource = resources.resource()
val originalAsset = resources.asset()
val loaderResource = "stringOne".openLoader()
val loaderAsset = "assetOne".openLoader(dataType = DataType.ASSET)
addLoader(loaderResource)
addLoader(loaderAsset)
val oldLoaders = resources.loaders
val oldResource = resources.resource()
val oldAsset = resources.asset()
assertThat(oldResource).isNotEqualTo(originalResource)
assertThat(oldAsset).isNotEqualTo(originalAsset)
val newResources = block()
val newLoaders = newResources.loaders
val newResource = newResources.resource()
val newAsset = newResources.asset()
assertThat(newResource).isEqualTo(oldResource)
assertThat(newAsset).isEqualTo(oldAsset)
assertThat(newLoaders).isEqualTo(oldLoaders)
}
// Copied from overlaytests LocalOverlayManager
private fun enableOverlay(packageName: String, enable: Boolean) {
val executor = Executor { Thread(it).start() }
val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
if (executeShellCommand("cmd overlay list").contains(pattern)) {
// nothing to do, overlay already in the requested state
return
}
val oldApkPaths = resources.assets.apkPaths
val task = FutureTask {
while (true) {
if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
return@FutureTask true
}
Thread.sleep(10)
}
@Suppress("UNREACHABLE_CODE")
return@FutureTask false
}
val command = if (enable) "enable" else "disable"
executeShellCommand("cmd overlay $command $packageName")
executor.execute(task)
assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
}
private fun executeShellCommand(command: String): String {
val uiAutomation = instrumentation.uiAutomation
val pfd = uiAutomation.executeShellCommand(command)
return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
}
}
class TestActivity : Activity() {
var callback: Callback? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(FrameLayout(this).apply {
setBackgroundColor(Color.BLUE)
})
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
callback?.onConfigurationChanged(newConfig)
}
interface Callback {
fun onConfigurationChanged(newConfig: Configuration)
}
}