1 /*
<lambda>null2  * Copyright 2023 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 androidx.tracing.perfetto.handshake
18 
19 import java.io.File
20 import java.util.zip.ZipFile
21 
22 /**
23  * Sideloads the `libtracing_perfetto.so` file to a location available to the traced app
24  *
25  * The class solves the following sub-problems:
26  * - knowing the right location to place the binaries
27  * - knowing how to extract the binaries from an AAR or APK, including choosing the right build
28  *   variant for the device (e.g. arm64-v8a) from the archive
29  * - knowing how to handle device IO permissions, e.g. to allow a Benchmark app place the Perfetto
30  *   binaries in a location accessible by the benchmarked app (we use `shell` for this)
31  */
32 internal class PerfettoSdkSideloader(private val packageName: String) {
33 
34     /**
35      * Sideloads `libtracing_perfetto.so` from a ZIP source to a location available to the traced
36      * app
37      *
38      * @param sourceZipFile either an AAR or an APK containing `libtracing_perfetto.so`
39      * @param shellCommandExecutor function capable of executing adb shell commands (used to
40      *   determine the device ABI)
41      * @param tempDirectory a directory directly accessible to the process (used for extraction of
42      *   the binaries from the zip)
43      * @param moveLibFileFromTmpDirToAppDir a function capable of moving the binary file from the
44      *   [tempDirectory] and an app accessible folder
45      * @return location where the library file was sideloaded to
46      */
47     fun sideloadFromZipFile(
48         sourceZipFile: File,
49         tempDirectory: File,
50         shellCommandExecutor: ShellCommandExecutor,
51         moveLibFileFromTmpDirToAppDir: FileMover
52     ): File {
53         val abi = getDeviceAbi(shellCommandExecutor)
54         val tmpFile = extractPerfettoBinaryFromZip(sourceZipFile, tempDirectory, abi)
55         return sideloadSoFile(tmpFile, moveLibFileFromTmpDirToAppDir)
56     }
57 
58     /**
59      * Sideloads `libtracing_perfetto.so` to a location available to the traced app
60      *
61      * @param libFile `libtracing_perfetto.so` file
62      * @param moveLibFileToAppDir a function moving the [libFile] to an app accessible folder
63      * @return location where the library file was sideloaded to
64      */
65     private fun sideloadSoFile(libFile: File, moveLibFileToAppDir: FileMover): File {
66         val dstFile = libFileForPackageName(packageName)
67         moveLibFileToAppDir(libFile, dstFile)
68         return dstFile
69     }
70 
71     private fun extractPerfettoBinaryFromZip(sourceZip: File, outputDir: File, abi: String): File {
72         val outputFile = outputDir.resolve(libFileName)
73         val rxLibPathInsideZip = Regex(".*(lib|jni)/[^/]*$abi[^/]*/$libFileName")
74         val zipFile = ZipFile(sourceZip)
75         val entry =
76             zipFile.entries().asSequence().firstOrNull { it.name.matches(rxLibPathInsideZip) }
77                 ?: throw IllegalStateException(
78                     "Unable to locate $libFileName required to enable Perfetto SDK. " +
79                         "Tried inside ${sourceZip.absolutePath}."
80                 )
81         zipFile.getInputStream(entry).use { inputStream ->
82             outputFile.outputStream().use { outputStream -> inputStream.copyTo(outputStream) }
83         }
84         return outputFile
85     }
86 
87     private fun getDeviceAbi(executeShellCommand: ShellCommandExecutor): String =
88         executeShellCommand("getprop ro.product.cpu.abilist")
89             .split(",")
90             .plus(executeShellCommand("getprop ro.product.cpu.abi"))
91             .first()
92             .trim()
93 
94     private companion object {
95         private const val libFileName = "libtracing_perfetto.so"
96 
97         fun libFileForPackageName(packageName: String) =
98             File("/sdcard/Android/media/$packageName/$libFileName")
99     }
100 }
101 
102 internal typealias FileMover = (srcFile: File, dstFile: File) -> Unit
103 
104 internal typealias ShellCommandExecutor = (command: String) -> String
105