blob: 0cc56d721651cf14c55984108f9ab845bc7db888 [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.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.loader.ResourcesLoader
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.os.IBinder
import androidx.test.rule.ActivityTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.Collections
/**
* Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
* the behavior being verified isn't specific to any resource type. As long as it can pass an
* equals check.
*
* Currently tests strings and dimens since String and any Number seemed most relevant to verify.
*/
@RunWith(Parameterized::class)
class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
@get:Rule
private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
companion object {
@Parameterized.Parameters(name = "{1} {0}")
@JvmStatic
fun parameters(): Array<Any> {
val parameters = mutableListOf<Parameter>()
// R.string
parameters += Parameter(
{ getString(android.R.string.cancel) },
"stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
"stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
"stringThree", { "SomeRidiculouslyUnlikelyStringThree" },
"stringFour", { "SomeRidiculouslyUnlikelyStringFour" },
listOf(DataType.APK, DataType.ARSC)
)
// R.dimen
parameters += Parameter(
{ getDimensionPixelSize(android.R.dimen.app_icon_size) },
"dimenOne", { 100.dpToPx(resources) },
"dimenTwo", { 200.dpToPx(resources) },
"dimenThree", { 300.dpToPx(resources) },
"dimenFour", { 400.dpToPx(resources) },
listOf(DataType.APK, DataType.ARSC)
)
// File in the assets directory
parameters += Parameter(
{ assets.open("Asset.txt").reader().readText() },
"assetOne", { "assetOne" },
"assetTwo", { "assetTwo" },
"assetFour", { "assetFour" },
"assetThree", { "assetThree" },
listOf(DataType.ASSET)
)
// From assets directory returning file descriptor
parameters += Parameter(
{ assets.openFd("Asset.txt").readText() },
"assetOne", { "assetOne" },
"assetTwo", { "assetTwo" },
"assetFour", { "assetFour" },
"assetThree", { "assetThree" },
listOf(DataType.ASSET_FD)
)
// From root directory returning file descriptor
parameters += Parameter(
{ assets.openNonAssetFd("NonAsset.txt").readText() },
"NonAssetOne", { "NonAssetOne" },
"NonAssetTwo", { "NonAssetTwo" },
"NonAssetThree", { "NonAssetThree" },
"NonAssetFour", { "NonAssetFour" },
listOf(DataType.NON_ASSET)
)
// Asset as compiled XML drawable
parameters += Parameter(
{ (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
"nonAssetDrawableOne", { Color.parseColor("#000001") },
"nonAssetDrawableTwo", { Color.parseColor("#000002") },
"nonAssetDrawableThree", { Color.parseColor("#000003") },
"nonAssetDrawableFour", { Color.parseColor("#000004") },
listOf(DataType.NON_ASSET_DRAWABLE)
)
// Asset as compiled bitmap drawable
parameters += Parameter(
{
(getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
.bitmap.getColor(0, 0).toArgb()
},
"nonAssetBitmapRed", { Color.RED },
"nonAssetBitmapGreen", { Color.GREEN },
"nonAssetBitmapBlue", { Color.BLUE },
"nonAssetBitmapWhite", { Color.WHITE },
listOf(DataType.NON_ASSET_BITMAP)
)
// Asset as compiled XML layout
parameters += Parameter(
{ getLayout(R.layout.layout).advanceToRoot().name },
"layoutOne", { "RelativeLayout" },
"layoutTwo", { "LinearLayout" },
"layoutThree", { "FrameLayout" },
"layoutFour", { "TableLayout" },
listOf(DataType.NON_ASSET_LAYOUT)
)
// Isolated resource split
parameters += Parameter(
{ getString(R.string.split_overlaid) },
"split_one", { "Split ONE Overlaid" },
"split_two", { "Split TWO Overlaid" },
"split_three", { "Split THREE Overlaid" },
"split_four", { "Split FOUR Overlaid" },
listOf(DataType.SPLIT)
)
return parameters.flatMap { parameter ->
parameter.dataTypes.map { dataType ->
arrayOf(dataType, parameter)
}
}.toTypedArray()
}
}
@Suppress("LateinitVarOverridesLateinitVar")
@field:Parameterized.Parameter(0)
override lateinit var dataType: DataType
@field:Parameterized.Parameter(1)
lateinit var parameter: Parameter
private val valueOne by lazy { parameter.valueOne(this) }
private val valueTwo by lazy { parameter.valueTwo(this) }
private val valueThree by lazy { parameter.valueThree(this) }
private val valueFour by lazy { parameter.valueFour(this) }
private fun openOne() = parameter.providerOne.openProvider()
private fun openTwo() = parameter.providerTwo.openProvider()
private fun openThree() = parameter.providerThree.openProvider()
private fun openFour() = parameter.providerFour.openProvider()
// Class method for syntax highlighting purposes
private fun getValue(c: Context = context) = parameter.getValue(c.resources)
@Test
fun assertValueUniqueness() {
// Ensure the parameters are valid in case of coding errors
val original = getValue()
assertNotEquals(valueOne, original)
assertNotEquals(valueTwo, original)
assertNotEquals(valueThree, original)
assertNotEquals(valueFour, original)
assertNotEquals(valueTwo, valueOne)
assertNotEquals(valueThree, valueOne)
assertNotEquals(valueFour, valueOne)
assertNotEquals(valueThree, valueTwo)
assertNotEquals(valueFour, valueTwo)
assertNotEquals(valueFour, valueThree)
}
@Test
fun addMultipleProviders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
loader.addProvider(testTwo)
assertEquals(valueTwo, getValue())
loader.removeProvider(testOne)
assertEquals(valueTwo, getValue())
loader.removeProvider(testTwo)
assertEquals(originalValue, getValue())
}
@Test
fun addMultipleLoaders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
val loader2 = ResourcesLoader()
resources.addLoader(loader1)
loader1.addProvider(testOne)
assertEquals(valueOne, getValue())
resources.addLoader(loader2)
loader2.addProvider(testTwo)
assertEquals(valueTwo, getValue())
resources.removeLoader(loader1)
assertEquals(valueTwo, getValue())
resources.removeLoader(loader2)
assertEquals(originalValue, getValue())
}
@Test
fun setMultipleProviders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.providers = listOf(testOne, testTwo)
assertEquals(valueTwo, getValue())
loader.removeProvider(testTwo)
assertEquals(valueOne, getValue())
loader.providers = Collections.emptyList()
assertEquals(originalValue, getValue())
}
@Test
fun setMultipleLoaders() {
val originalValue = getValue()
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
resources.loaders = listOf(loader1, loader2)
assertEquals(valueTwo, getValue())
resources.removeLoader(loader2)
assertEquals(valueOne, getValue())
resources.loaders = Collections.emptyList()
assertEquals(originalValue, getValue())
}
@Test(expected = UnsupportedOperationException::class)
fun getProvidersDoesNotLeakMutability() {
val testOne = openOne()
val loader = ResourcesLoader()
val providers = loader.providers
providers += testOne
}
@Test(expected = UnsupportedOperationException::class)
fun getLoadersDoesNotLeakMutability() {
val loaders = resources.loaders
loaders += ResourcesLoader()
}
@Test
fun alreadyAddedProviderNoOps() {
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
loader.addProvider(testOne)
assertEquals(2, loader.providers.size)
assertEquals(loader.providers[0], testOne)
assertEquals(loader.providers[1], testTwo)
}
@Test
fun alreadyAddedLoaderNoOps() {
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
resources.addLoader(loader1)
resources.addLoader(loader2)
resources.addLoader(loader1)
assertEquals(2, resources.loaders.size)
assertEquals(resources.loaders[0], loader1)
assertEquals(resources.loaders[1], loader2)
}
@Test
fun repeatedRemoveProviderNoOps() {
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
loader.removeProvider(testOne)
loader.removeProvider(testOne)
assertEquals(1, loader.providers.size)
assertEquals(loader.providers[0], testTwo)
}
@Test
fun repeatedRemoveLoaderNoOps() {
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
resources.loaders = listOf(loader1, loader2)
resources.removeLoader(loader1)
resources.removeLoader(loader1)
assertEquals(1, resources.loaders.size)
assertEquals(resources.loaders[0], loader2)
}
@Test
fun repeatedSetProvider() {
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.providers = listOf(testOne, testTwo)
loader.providers = listOf(testOne, testTwo)
assertEquals(2, loader.providers.size)
assertEquals(loader.providers[0], testOne)
assertEquals(loader.providers[1], testTwo)
}
@Test
fun repeatedSetLoaders() {
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
resources.loaders = listOf(loader1, loader2)
resources.loaders = listOf(loader1, loader2)
assertEquals(2, resources.loaders.size)
assertEquals(resources.loaders[0], loader1)
assertEquals(resources.loaders[1], loader2)
}
@Test
fun reorderProviders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
assertEquals(valueTwo, getValue())
loader.removeProvider(testOne)
assertEquals(valueTwo, getValue())
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
loader.removeProvider(testTwo)
assertEquals(valueOne, getValue())
loader.removeProvider(testOne)
assertEquals(originalValue, getValue())
}
@Test
fun reorderLoaders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
loader1.addProvider(testOne)
val loader2 = ResourcesLoader()
loader2.addProvider(testTwo)
resources.addLoader(loader1)
resources.addLoader(loader2)
assertEquals(valueTwo, getValue())
resources.removeLoader(loader1)
assertEquals(valueTwo, getValue())
resources.addLoader(loader1)
assertEquals(valueOne, getValue())
resources.removeLoader(loader2)
assertEquals(valueOne, getValue())
resources.removeLoader(loader1)
assertEquals(originalValue, getValue())
}
@Test
fun reorderMultipleLoadersAndProviders() {
val testOne = openOne()
val testTwo = openTwo()
val testThree = openThree()
val testFour = openFour()
val loader1 = ResourcesLoader()
loader1.providers = listOf(testOne, testTwo)
val loader2 = ResourcesLoader()
loader2.providers = listOf(testThree, testFour)
resources.loaders = listOf(loader1, loader2)
assertEquals(valueFour, getValue())
resources.loaders = listOf(loader2, loader1)
assertEquals(valueTwo, getValue())
loader1.removeProvider(testTwo)
assertEquals(valueOne, getValue())
loader1.removeProvider(testOne)
assertEquals(valueFour, getValue())
}
private fun createContext(context: Context, id: Int): Context {
val overrideConfig = Configuration()
overrideConfig.orientation = Int.MAX_VALUE - id
return context.createConfigurationContext(overrideConfig)
}
@Test
fun copyContextLoaders() {
val originalValue = getValue()
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
resources.loaders = listOf(loader1)
assertEquals(valueOne, getValue())
// The child context should include the loaders of the original context.
val childContext = createContext(context, 0)
assertEquals(valueOne, getValue(childContext))
// Changing the loaders of the child context should not affect the original context.
childContext.resources.loaders = listOf(loader1, loader2)
assertEquals(valueOne, getValue())
assertEquals(valueTwo, getValue(childContext))
// Changing the loaders of the original context should not affect the child context.
resources.removeLoader(loader1)
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
// A new context created from the original after an update to the original's loaders should
// have the updated loaders.
val originalPrime = createContext(context, 2)
assertEquals(originalValue, getValue(originalPrime))
// A new context created from the child context after an update to the child's loaders
// should have the updated loaders.
val childPrime = createContext(childContext, 1)
assertEquals(valueTwo, getValue(childPrime))
}
@Test
fun loaderUpdatesAffectContexts() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
resources.addLoader(loader)
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
val childContext = createContext(context, 0)
assertEquals(valueOne, getValue(childContext))
// Adding a provider to a loader affects all contexts that use the loader.
loader.addProvider(testTwo)
assertEquals(valueTwo, getValue())
assertEquals(valueTwo, getValue(childContext))
// Changes to the loaders for a context do not affect providers.
resources.clearLoaders()
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
val childContext2 = createContext(context, 1)
assertEquals(originalValue, getValue())
assertEquals(originalValue, getValue(childContext2))
childContext2.resources.addLoader(loader)
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
assertEquals(valueTwo, getValue(childContext2))
}
@Test
fun appLoadersIncludedInActivityContexts() {
val loader = ResourcesLoader()
loader.addProvider(openOne())
val applicationContext = context.applicationContext
applicationContext.resources.addLoader(loader)
assertEquals(valueOne, getValue(applicationContext))
val activity = mTestActivityRule.launchActivity(Intent())
assertEquals(valueOne, getValue(activity))
applicationContext.resources.clearLoaders()
}
@Test
fun loadersApplicationInfoChanged() {
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
val applicationContext = context.applicationContext
applicationContext.resources.addLoader(loader1)
assertEquals(valueOne, getValue(applicationContext))
var token: IBinder? = null
val activity = mTestActivityRule.launchActivity(Intent())
mTestActivityRule.runOnUiThread(Runnable {
token = activity.activityToken
val at = activity.activityThread
// The activity should have the loaders from the application.
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueOne, getValue(activity))
activity.resources.addLoader(loader2)
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueTwo, getValue(activity))
// Relaunches the activity.
at.handleApplicationInfoChanged(activity.applicationInfo)
})
mTestActivityRule.runOnUiThread(Runnable {
val activityThread = activity.activityThread
val newActivity = activityThread.getActivity(token)
// The loader added to the activity loaders should not be persisted.
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueOne, getValue(newActivity))
})
applicationContext.resources.clearLoaders()
}
@Test
fun multipleLoadersHaveSameProviders() {
val provider1 = openOne()
val loader1 = ResourcesLoader()
loader1.addProvider(provider1)
val loader2 = ResourcesLoader()
loader2.addProvider(provider1)
loader2.addProvider(openTwo())
resources.loaders = listOf(loader1, loader2)
assertEquals(valueTwo, getValue())
resources.loaders = listOf(loader2, loader1)
assertEquals(valueOne, getValue())
assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
}
@Test(expected = IllegalStateException::class)
fun cannotUseClosedProvider() {
val provider = openOne()
provider.close()
val loader = ResourcesLoader()
loader.addProvider(provider)
}
@Test(expected = IllegalStateException::class)
fun cannotCloseUsedProvider() {
val provider = openOne()
val loader = ResourcesLoader()
loader.addProvider(provider)
provider.close()
}
data class Parameter(
val getValue: Resources.() -> Any,
val providerOne: String,
val valueOne: ResourceLoaderValuesTest.() -> Any,
val providerTwo: String,
val valueTwo: ResourceLoaderValuesTest.() -> Any,
val providerThree: String,
val valueThree: ResourceLoaderValuesTest.() -> Any,
val providerFour: String,
val valueFour: ResourceLoaderValuesTest.() -> Any,
val dataTypes: List<DataType>
) {
override fun toString(): String {
val prefix = providerOne.commonPrefixWith(providerTwo)
return "$prefix${providerOne.removePrefix(prefix)}|${providerTwo.removePrefix(prefix)}"
}
}
}
class TestActivity : Activity()