1 /*
<lambda>null2  * Copyright (C) 2020 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 com.android.server.pm.test
18 
19 import com.android.internal.util.test.SystemPreparer
20 import com.android.tradefed.device.ITestDevice
21 import com.google.common.truth.Truth
22 import org.junit.rules.TemporaryFolder
23 import java.io.File
24 import java.io.FileOutputStream
25 
26 internal fun SystemPreparer.pushApk(javaResourceName: String, partition: Partition) =
27         pushResourceFile(javaResourceName, HostUtils.makePathForApk(javaResourceName, partition)
28                 .toString())
29 
30 internal fun SystemPreparer.deleteApkFolders(
31     partition: Partition,
32     vararg javaResourceNames: String
33 ) = apply {
34     javaResourceNames.forEach {
35         deleteFile(partition.baseAppFolder.resolve(it.removeSuffix(".apk")).toString())
36     }
37 }
38 
installJavaResourceApknull39 internal fun ITestDevice.installJavaResourceApk(
40     tempFolder: TemporaryFolder,
41     javaResource: String,
42     reinstall: Boolean = true,
43     extraArgs: Array<String> = emptyArray()
44 ): String? {
45     val file = HostUtils.copyResourceToHostFile(javaResource, tempFolder.newFile())
46     return installPackage(file, reinstall, *extraArgs)
47 }
48 
uninstallPackagesnull49 internal fun ITestDevice.uninstallPackages(vararg pkgNames: String) =
50         pkgNames.forEach { uninstallPackage(it) }
51 
52 /**
53  * Retry [block] a total of [maxAttempts] times, waiting [millisBetweenAttempts] milliseconds
54  * between each iteration, until a non-null result is returned, providing that result back to the
55  * caller.
56  *
57  * If an [AssertionError] is thrown by the [block] and a non-null result is never returned, that
58  * error will be re-thrown. This allows the use of [Truth.assertThat] to indicate success while
59  * providing a meaningful error message in case of failure.
60  */
retryUntilNonNullnull61 internal fun <T> retryUntilNonNull(
62     maxAttempts: Int = 10,
63     millisBetweenAttempts: Long = 1000,
64     block: () -> T?
65 ): T {
66     var attempt = 0
67     var failure: AssertionError? = null
68     while (attempt++ < maxAttempts) {
69         val result = try {
70             block()
71         } catch (e: AssertionError) {
72             failure = e
73             null
74         }
75 
76         if (result != null) {
77             return result
78         } else {
79             Thread.sleep(millisBetweenAttempts)
80         }
81     }
82 
83     throw failure ?: AssertionError("Never succeeded")
84 }
85 
retryUntilSuccessnull86 internal fun retryUntilSuccess(block: () -> Boolean) {
87     retryUntilNonNull { block().takeIf { it } }
88 }
89 
90 internal object HostUtils {
91 
getDataDirnull92     fun getDataDir(device: ITestDevice, pkgName: String) =
93             device.executeShellCommand("dumpsys package $pkgName")
94                     .lineSequence()
95                     .map(String::trim)
96                     .single { it.startsWith("dataDir=") }
97                     .removePrefix("dataDir=")
98 
makePathForApknull99     fun makePathForApk(fileName: String, partition: Partition) =
100             makePathForApk(File(fileName), partition)
101 
102     fun makePathForApk(file: File, partition: Partition) =
103             partition.baseAppFolder
104                     .resolve(file.nameWithoutExtension)
105                     .resolve(file.name)
106 
107     fun copyResourceToHostFile(javaResourceName: String, file: File): File {
108         javaClass.classLoader!!.getResource(javaResourceName).openStream().use { input ->
109             FileOutputStream(file).use { output ->
110                 input.copyTo(output)
111             }
112         }
113         return file
114     }
115 
116     /**
117      * dumpsys package and therefore device.getAppPackageInfo doesn't work immediately after reboot,
118      * so the following methods parse the package dump directly to see if the path matches.
119      */
120 
121     /**
122      * Reads the pm dump for a package name starting from the Packages: metadata section until
123      * the following section.
124      */
packageSectionnull125     fun packageSection(
126         device: ITestDevice,
127         pkgName: String,
128         sectionName: String = "Packages"
129     ) = device.executeShellCommand("pm dump $pkgName")
130             .lineSequence()
131             .dropWhile { !it.startsWith(sectionName) } // Wait until the header
132             .drop(1) // Drop the header itself
<lambda>null133             .takeWhile {
134                 // Until next top level header, a non-empty line that doesn't start with whitespace
135                 it.isEmpty() || it.first().isWhitespace()
136             }
137             .map(String::trim)
138 
getCodePathsnull139     fun getCodePaths(device: ITestDevice, pkgName: String) =
140             device.executeShellCommand("pm dump $pkgName")
141                     .lineSequence()
142                     .map(String::trim)
143                     .filter { it.startsWith("codePath=") }
<lambda>null144                     .map { it.removePrefix("codePath=") }
145                     .toList()
146 
userIdLineSequencenull147     private fun userIdLineSequence(device: ITestDevice, pkgName: String) =
148             packageSection(device, pkgName)
149                     .filter { it.startsWith("User ") }
150 
getUserIdToPkgEnabledStatenull151     fun getUserIdToPkgEnabledState(device: ITestDevice, pkgName: String) =
152             userIdLineSequence(device, pkgName).associate {
153                 val userId = it.removePrefix("User ")
154                         .takeWhile(Char::isDigit)
155                         .toInt()
156                 val enabled = it.substringAfter("enabled=")
157                         .takeWhile(Char::isDigit)
158                         .toInt()
159                         .let {
160                             when (it) {
161                                 0, 1 -> true
162                                 else -> false
163                             }
164                         }
165                 userId to enabled
166             }
167 
getUserIdToPkgInstalledStatenull168     fun getUserIdToPkgInstalledState(device: ITestDevice, pkgName: String) =
169             userIdLineSequence(device, pkgName).associate {
170                 val userId = it.removePrefix("User ")
171                         .takeWhile(Char::isDigit)
172                         .toInt()
173                 val installed = it.substringAfter("installed=")
174                         .takeWhile { !it.isWhitespace() }
175                         .toBoolean()
176                 userId to installed
177             }
178 }
179