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