1 /*
<lambda>null2  * 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 
17 package android.content.res.loader.cts
18 
19 import android.content.Context
20 import android.content.res.AssetFileDescriptor
21 import android.content.res.Configuration
22 import android.content.res.Resources
23 import android.content.res.loader.AssetsProvider
24 import android.content.res.loader.ResourcesProvider
25 import android.os.ParcelFileDescriptor
26 import android.system.Os
27 import android.util.ArrayMap
28 import androidx.test.InstrumentationRegistry
29 import org.json.JSONObject
30 import org.junit.After
31 import org.junit.Assert.assertNull
32 import org.junit.Before
33 import java.io.Closeable
34 import java.io.FileOutputStream
35 import java.io.File
36 import java.io.FileDescriptor
37 import java.util.zip.ZipInputStream
38 
39 abstract class ResourcesLoaderTestBase {
40     protected val PROVIDER_ONE: String = "CtsResourcesLoaderTests_ProviderOne"
41     protected val PROVIDER_TWO: String = "CtsResourcesLoaderTests_ProviderTwo"
42     protected val PROVIDER_THREE: String = "CtsResourcesLoaderTests_ProviderThree"
43     protected val PROVIDER_FOUR: String = "CtsResourcesLoaderTests_ProviderFour"
44     protected val PROVIDER_EMPTY: String = "empty"
45 
46     companion object {
47         /** Converts the map to a stable JSON string representation. */
48         fun mapToString(m: Map<String, String>): String {
49             return JSONObject(ArrayMap<Any?, Any?>().apply { putAll(m) }).toString()
50         }
51 
52         /** Creates a lambda that runs multiple resources queries and concatenates the results. */
53         fun query(queries: Map<String, (Resources) -> String>): Resources.() -> String {
54             return {
55                 val resultMap = ArrayMap<String, String>()
56                 queries.forEach { q ->
57                     resultMap[q.key] = try {
58                         q.value.invoke(this)
59                     } catch (e: Exception) {
60                         e.javaClass.simpleName
61                     }
62                 }
63                 mapToString(resultMap)
64             }
65         }
66     }
67 
68     // Data type of the current test iteration
69     open lateinit var dataType: DataType
70 
71     protected lateinit var context: Context
72     protected lateinit var resources: Resources
73 
74     // Track opened streams and ResourcesProviders to close them after testing
75     private val openedObjects = mutableListOf<Closeable>()
76 
77     @Before
78     fun setUpBase() {
79         context = InstrumentationRegistry.getTargetContext()
80                 .createConfigurationContext(Configuration())
81         resources = context.resources
82     }
83 
84     @After
85     fun removeAllLoaders() {
86         resources.clearLoaders()
87         context.applicationContext.resources.clearLoaders()
88         openedObjects.forEach {
89             try {
90                 it.close()
91             } catch (ignored: Exception) {
92             }
93         }
94     }
95 
96     protected fun String.openProvider(
97         dataType: DataType,
98         assetsProvider: MemoryAssetsProvider? = null
99     ): ResourcesProvider {
100         if (assetsProvider != null) {
101             openedObjects += assetsProvider
102         }
103         return when (dataType) {
104             DataType.APK_DISK_FD_NO_ASSETS_PROVIDER -> {
105                 assertNull(assetsProvider)
106                 val file = context.copiedAssetFile("$this.apk")
107                 ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd)).also {
108                     file.close()
109                 }
110             }
111             DataType.APK_DISK_FD -> {
112                 val file = context.copiedAssetFile("$this.apk")
113                 ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd),
114                         assetsProvider).also {
115                     file.close()
116                 }
117             }
118             DataType.APK_DISK_FD_OFFSETS -> {
119                 val asset = context.assets.openFd("$this.apk")
120                 ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
121                         asset.length, assetsProvider).also {
122                     asset.close()
123                 }
124             }
125             DataType.ARSC_DISK_FD -> {
126                 val file = context.copiedAssetFile("$this.arsc")
127                 ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd),
128                         assetsProvider).also {
129                     file.close()
130                 }
131             }
132             DataType.ARSC_DISK_FD_OFFSETS -> {
133                 val asset = context.assets.openFd("$this.arsc")
134                 ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
135                         asset.length, assetsProvider).also {
136                     asset.close()
137                 }
138             }
139             DataType.APK_RAM_OFFSETS -> {
140                 val asset = context.assets.openFd("$this.apk")
141                 val leadingGarbageSize = 100L
142                 val trailingGarbageSize = 55L
143                 val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
144                         trailingGarbageSize.toInt())
145                 ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
146                         assetsProvider).also {
147                     asset.close()
148                     fd.close()
149                 }
150             }
151             DataType.APK_RAM_FD -> {
152                 val asset = context.assets.openFd("$this.apk")
153                 var fd = loadAssetIntoMemory(asset)
154                 ResourcesProvider.loadFromApk(fd, assetsProvider).also {
155                     asset.close()
156                     fd.close()
157                 }
158             }
159             DataType.ARSC_RAM_MEMORY -> {
160                 val asset = context.assets.openFd("$this.arsc")
161                 var fd = loadAssetIntoMemory(asset)
162                 ResourcesProvider.loadFromTable(fd, assetsProvider).also {
163                     asset.close()
164                     fd.close()
165                 }
166             }
167             DataType.ARSC_RAM_MEMORY_OFFSETS -> {
168                 val asset = context.assets.openFd("$this.arsc")
169                 val leadingGarbageSize = 100L
170                 val trailingGarbageSize = 55L
171                 val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
172                         trailingGarbageSize.toInt())
173                 ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
174                         assetsProvider).also {
175                     asset.close()
176                     fd.close()
177                 }
178             }
179             DataType.EMPTY -> {
180                 if (equals(PROVIDER_EMPTY)) {
181                     ResourcesProvider.empty(EmptyAssetsProvider())
182                 } else {
183                     if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this))
184                         else ResourcesProvider.empty(assetsProvider)
185                 }
186             }
187             DataType.DIRECTORY -> {
188                 ResourcesProvider.loadFromDirectory(zipToDir("$this.apk").absolutePath,
189                         assetsProvider)
190             }
191             DataType.SPLIT -> {
192                 ResourcesProvider.loadFromSplit(context, "${this}_Split")
193             }
194         }
195     }
196 
197     class EmptyAssetsProvider : AssetsProvider
198 
199     /** An AssetsProvider that reads from a zip asset. */
200     inner class ZipAssetsProvider(val providerName: String) : AssetsProvider {
201         val root: File = zipToDir("$providerName.apk")
202 
203         override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
204             val f = File(root, path)
205             return if (f.exists()) AssetFileDescriptor(
206                     ParcelFileDescriptor.open(File(root, path),
207                             ParcelFileDescriptor.MODE_READ_ONLY), 0,
208                     AssetFileDescriptor.UNKNOWN_LENGTH) else null
209         }
210     }
211 
212     /** AssetsProvider for testing that returns file descriptors to files in RAM. */
213     class MemoryAssetsProvider : AssetsProvider, Closeable {
214         var loadAssetResults = HashMap<String, FileDescriptor>()
215 
216         fun addLoadAssetFdResult(path: String, value: String) = apply {
217             val fd = Os.memfd_create(path, 0)
218             val valueBytes = value.toByteArray()
219             Os.write(fd, valueBytes, 0, valueBytes.size)
220             loadAssetResults[path] = fd
221         }
222 
223         override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
224             return if (loadAssetResults.containsKey(path)) AssetFileDescriptor(
225                     ParcelFileDescriptor.dup(loadAssetResults[path]), 0,
226                     AssetFileDescriptor.UNKNOWN_LENGTH) else null
227         }
228 
229         override fun close() {
230             for (f in loadAssetResults.values) {
231                 Os.close(f)
232             }
233         }
234     }
235 
236     /** Extracts an archive-based asset into a directory on disk. */
237     private fun zipToDir(name: String): File {
238         val root = File(context.filesDir, name.split('.')[0])
239         if (root.exists()) {
240             return root
241         }
242 
243         root.mkdir()
244         ZipInputStream(context.assets.open(name)).use { zis ->
245             while (true) {
246                 val entry = zis.nextEntry ?: break
247                 val file = File(root, entry.name)
248                 if (entry.isDirectory) {
249                     continue
250                 }
251 
252                 file.parentFile.mkdirs()
253                 file.outputStream().use { output ->
254                     var b = zis.read()
255                     while (b != -1) {
256                         output.write(b)
257                         b = zis.read()
258                     }
259                 }
260             }
261         }
262         return root
263     }
264 
265     /** Loads the asset into a temporary file stored in RAM. */
266     private fun loadAssetIntoMemory(
267         asset: AssetFileDescriptor,
268         leadingGarbageSize: Int = 0,
269         trailingGarbageSize: Int = 0
270     ): ParcelFileDescriptor {
271         val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */)
272         val fd = ParcelFileDescriptor.dup(originalFd)
273         Os.close(originalFd)
274 
275         val input = asset.createInputStream()
276         FileOutputStream(fd.fileDescriptor).use { output ->
277             // Add garbage before the APK data
278             for (i in 0 until leadingGarbageSize) {
279                 output.write(Math.random().toInt())
280             }
281 
282             for (i in 0 until asset.length.toInt()) {
283                 output.write(input.read())
284             }
285 
286             // Add garbage after the APK data
287             for (i in 0 until trailingGarbageSize) {
288                 output.write(Math.random().toInt())
289             }
290         }
291 
292         return fd
293     }
294 
295     enum class DataType {
296         APK_DISK_FD_NO_ASSETS_PROVIDER,
297         APK_DISK_FD,
298         APK_DISK_FD_OFFSETS,
299         APK_RAM_FD,
300         APK_RAM_OFFSETS,
301         ARSC_DISK_FD,
302         ARSC_DISK_FD_OFFSETS,
303         ARSC_RAM_MEMORY,
304         ARSC_RAM_MEMORY_OFFSETS,
305         EMPTY,
306         DIRECTORY,
307         SPLIT
308     }
309 }
310