blob: 0c3d34e686dd58b291b8c7cf67b2cdbf06842ae6 [file] [log] [blame]
Winson9947f1e2019-08-16 10:20:39 -07001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content.res.loader.test
18
19import android.app.Activity
20import android.app.Instrumentation
21import android.app.UiAutomation
22import android.content.res.Configuration
23import android.content.res.Resources
24import android.graphics.Color
25import android.os.Bundle
26import android.os.ParcelFileDescriptor
27import android.widget.FrameLayout
28import androidx.test.InstrumentationRegistry
29import androidx.test.rule.ActivityTestRule
30import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
31import androidx.test.runner.lifecycle.Stage
32import com.google.common.truth.Truth.assertThat
33import org.junit.After
Winson9947f1e2019-08-16 10:20:39 -070034import org.junit.Before
35import org.junit.Rule
36import org.junit.Test
37import org.junit.runner.RunWith
38import org.junit.runners.Parameterized
39import java.util.Arrays
40import java.util.concurrent.CountDownLatch
41import java.util.concurrent.Executor
42import java.util.concurrent.FutureTask
43import java.util.concurrent.TimeUnit
44
Winson9947f1e2019-08-16 10:20:39 -070045@RunWith(Parameterized::class)
46class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
47
48 companion object {
49 private const val TIMEOUT = 30L
50 private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
51
52 @JvmStatic
53 @Parameterized.Parameters(name = "{0}")
54 fun data() = arrayOf(DataType.APK, DataType.ARSC)
55 }
56
57 @field:Parameterized.Parameter(0)
58 override lateinit var dataType: DataType
59
60 @get:Rule
61 val activityRule: ActivityTestRule<TestActivity> =
62 ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
63
64 // Redirect to the Activity's resources
65 override val resources: Resources
66 get() = activityRule.getActivity().resources
67
68 private val activity: TestActivity
69 get() = activityRule.getActivity()
70
71 private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
72
73 @Before
74 @After
75 fun disableOverlay() {
Winsonc932ff22019-12-05 16:42:54 -080076 enableOverlay(OVERLAY_PACKAGE, false)
Winson9947f1e2019-08-16 10:20:39 -070077 }
78
79 @Test
80 fun activityRecreate() = verifySameBeforeAndAfter {
81 val oldActivity = activity
82 var newActivity: Activity? = null
83 instrumentation.runOnMainSync { oldActivity.recreate() }
84 instrumentation.waitForIdleSync()
85 instrumentation.runOnMainSync {
86 newActivity = ActivityLifecycleMonitorRegistry.getInstance()
87 .getActivitiesInStage(Stage.RESUMED)
88 .single()
89 }
90
91 assertThat(newActivity).isNotNull()
92 assertThat(newActivity).isNotSameAs(oldActivity)
93
94 // Return the new resources to assert on
95 return@verifySameBeforeAndAfter newActivity!!.resources
96 }
97
98 @Test
99 fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
100 val latch = CountDownLatch(1)
101 val oldConfig = Configuration().apply { setTo(resources.configuration) }
102 var changedConfig: Configuration? = null
103
104 activity.callback = object : TestActivity.Callback {
105 override fun onConfigurationChanged(newConfig: Configuration) {
106 changedConfig = newConfig
107 latch.countDown()
108 }
109 }
110
111 val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
112 val newRotation = if (isPortrait) {
113 UiAutomation.ROTATION_FREEZE_90
114 } else {
115 UiAutomation.ROTATION_FREEZE_0
116 }
117
118 instrumentation.uiAutomation.setRotation(newRotation)
119
120 assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
121 assertThat(changedConfig).isNotEqualTo(oldConfig)
122 return@verifySameBeforeAndAfter activity.resources
123 }
124
125 @Test
126 fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
127 assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
128
129 enableOverlay(OVERLAY_PACKAGE, true)
130
131 assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
132
133 return@verifySameBeforeAndAfter activity.resources
134 }
135
136 @Test
137 fun enableOverlayChildContextUnaffected() {
138 val childContext = activity.createConfigurationContext(Configuration())
139 val childResources = childContext.resources
140 val originalValue = childResources.getString(android.R.string.cancel)
141 assertThat(childResources.getString(R.string.loader_path_change_test))
142 .isEqualTo("Not overlaid")
143
144 verifySameBeforeAndAfter {
145 enableOverlay(OVERLAY_PACKAGE, true)
146 return@verifySameBeforeAndAfter activity.resources
147 }
148
149 // Loader not applied, but overlay change propagated
150 assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
151 assertThat(childResources.getString(R.string.loader_path_change_test))
152 .isEqualTo("Overlaid")
153 }
154
155 // All these tests assert for the exact same loaders/values, so extract that logic out
156 private fun verifySameBeforeAndAfter(block: () -> Resources) {
Winsonc932ff22019-12-05 16:42:54 -0800157 fun Resources.resource() = this.getString(android.R.string.cancel)
158 fun Resources.asset() = this.assets.open("Asset.txt").reader().readText()
Winson9947f1e2019-08-16 10:20:39 -0700159
Winsonc932ff22019-12-05 16:42:54 -0800160 val originalResource = resources.resource()
161 val originalAsset = resources.asset()
Winson9947f1e2019-08-16 10:20:39 -0700162
Winsonc932ff22019-12-05 16:42:54 -0800163 val loaderResource = "stringOne".openLoader()
164 val loaderAsset = "assetOne".openLoader(dataType = DataType.ASSET)
165 addLoader(loaderResource)
166 addLoader(loaderAsset)
Winson9947f1e2019-08-16 10:20:39 -0700167
168 val oldLoaders = resources.loaders
Winsonc932ff22019-12-05 16:42:54 -0800169 val oldResource = resources.resource()
170 val oldAsset = resources.asset()
Winson9947f1e2019-08-16 10:20:39 -0700171
Winsonc932ff22019-12-05 16:42:54 -0800172 assertThat(oldResource).isNotEqualTo(originalResource)
173 assertThat(oldAsset).isNotEqualTo(originalAsset)
Winson9947f1e2019-08-16 10:20:39 -0700174
175 val newResources = block()
176
177 val newLoaders = newResources.loaders
Winsonc932ff22019-12-05 16:42:54 -0800178 val newResource = newResources.resource()
179 val newAsset = newResources.asset()
Winson9947f1e2019-08-16 10:20:39 -0700180
Winsonc932ff22019-12-05 16:42:54 -0800181 assertThat(newResource).isEqualTo(oldResource)
182 assertThat(newAsset).isEqualTo(oldAsset)
Winson9947f1e2019-08-16 10:20:39 -0700183 assertThat(newLoaders).isEqualTo(oldLoaders)
184 }
185
186 // Copied from overlaytests LocalOverlayManager
187 private fun enableOverlay(packageName: String, enable: Boolean) {
188 val executor = Executor { Thread(it).start() }
189 val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
190 if (executeShellCommand("cmd overlay list").contains(pattern)) {
191 // nothing to do, overlay already in the requested state
192 return
193 }
194
195 val oldApkPaths = resources.assets.apkPaths
196 val task = FutureTask {
197 while (true) {
198 if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
199 return@FutureTask true
200 }
201 Thread.sleep(10)
202 }
203
204 @Suppress("UNREACHABLE_CODE")
205 return@FutureTask false
206 }
207
208 val command = if (enable) "enable" else "disable"
209 executeShellCommand("cmd overlay $command $packageName")
210 executor.execute(task)
211 assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
212 }
213
214 private fun executeShellCommand(command: String): String {
215 val uiAutomation = instrumentation.uiAutomation
216 val pfd = uiAutomation.executeShellCommand(command)
217 return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
218 }
219}
220
221class TestActivity : Activity() {
222
223 var callback: Callback? = null
224
225 override fun onCreate(savedInstanceState: Bundle?) {
226 super.onCreate(savedInstanceState)
227
228 setContentView(FrameLayout(this).apply {
229 setBackgroundColor(Color.BLUE)
230 })
231 }
232
233 override fun onConfigurationChanged(newConfig: Configuration) {
234 super.onConfigurationChanged(newConfig)
235 callback?.onConfigurationChanged(newConfig)
236 }
237
238 interface Callback {
239 fun onConfigurationChanged(newConfig: Configuration)
240 }
241}