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
18 
19 import android.content.BroadcastReceiver
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.pm.PackageManager
23 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
24 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
25 import java.io.File
26 import java.util.Properties
27 
28 /**
29  * Config for enabling tracing at app startup
30  *
31  * @param libFilePath Path to the optionally sideloaded `libtracing_perfetto.so` file
32  * @param isPersistent Determines whether tracing should remain enabled (sticky) between app runs
33  */
34 internal data class StartupTracingConfig(val libFilePath: String?, val isPersistent: Boolean)
35 
36 /**
37  * Hack used by [StartupTracingConfigStore] to perform a fast check whether there is a
38  * [StartupTracingConfig] present. Relies on [PackageManager.getComponentEnabledSetting] and a dummy
39  * [BroadcastReceiver] component.
40  */
41 private abstract class StartupTracingConfigStoreIsEnabledGate : BroadcastReceiver() {
42     companion object {
43         fun enable(context: Context) = setEnabledSetting(context, true)
44 
45         fun disable(context: Context) = setEnabledSetting(context, false)
46 
47         private fun setEnabledSetting(context: Context, enabled: Boolean) {
48             context.packageManager.setComponentEnabledSetting(
49                 context.componentName,
50                 if (enabled) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED,
51                 PackageManager.DONT_KILL_APP
52             )
53         }
54 
55         fun isEnabled(context: Context): Boolean =
56             context.packageManager.getComponentEnabledSetting(context.componentName) ==
57                 COMPONENT_ENABLED_STATE_ENABLED
58 
59         private val Context.componentName
60             get() = ComponentName(this, StartupTracingConfigStoreIsEnabledGate::class.java.name)
61     }
62 }
63 
64 internal object StartupTracingConfigStore {
65     private const val KEY_IS_PERSISTENT = "isPersistent"
66     private const val KEY_LIB_FILE_PATH = "libtracingPerfettoFilePath"
67     private const val STARTUP_CONFIG_FILE_NAME = "libtracing_perfetto_startup.properties"
68 
startupConfigFileForPackageNamenull69     private fun startupConfigFileForPackageName(packageName: String): File =
70         File("/sdcard/Android/media/$packageName/$STARTUP_CONFIG_FILE_NAME")
71 
72     /** Loads the config */
73     fun load(context: Context): StartupTracingConfig? {
74         // use the fast-check-gate value
75         if (!StartupTracingConfigStoreIsEnabledGate.isEnabled(context)) return null
76 
77         // read the config from file
78         val propertiesFile = startupConfigFileForPackageName(context.packageName)
79         if (!propertiesFile.exists()) return null
80         val properties = Properties()
81         propertiesFile.reader().use { properties.load(it) }
82         return StartupTracingConfig(
83             properties.getProperty(KEY_LIB_FILE_PATH),
84             properties.getProperty(KEY_IS_PERSISTENT).toBoolean()
85         )
86     }
87 
88     /** Stores the config */
StartupTracingConfignull89     fun StartupTracingConfig.store(context: Context) {
90         startupConfigFileForPackageName(context.packageName).bufferedWriter().use { writer ->
91             Properties()
92                 .also {
93                     it.setProperty(KEY_LIB_FILE_PATH, libFilePath)
94                     it.setProperty(KEY_IS_PERSISTENT, isPersistent.toString())
95                 }
96                 .store(writer, null)
97         }
98         StartupTracingConfigStoreIsEnabledGate.enable(context) // update the fast-check-gate value
99     }
100 
101     /** Deletes the config */
clearnull102     fun clear(context: Context) {
103         StartupTracingConfigStoreIsEnabledGate.disable(context) // update the fast-check-gate value
104         startupConfigFileForPackageName(context.packageName).delete()
105     }
106 }
107