StartupTracingConfig.kt
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.tracing.perfetto
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
import java.io.File
import java.util.Properties
/**
* Config for enabling tracing at app startup
*
* @param libFilePath Path to the optionally sideloaded `libtracing_perfetto.so` file
* @param isPersistent Determines whether tracing should remain enabled (sticky) between app runs
*/
internal data class StartupTracingConfig(val libFilePath: String?, val isPersistent: Boolean)
/**
* Hack used by [StartupTracingConfigStore] to perform a fast check whether there is
* a [StartupTracingConfig] present. Relies on [PackageManager.getComponentEnabledSetting] and a
* dummy [BroadcastReceiver] component.
*/
private abstract class StartupTracingConfigStoreIsEnabledGate : BroadcastReceiver() {
companion object {
fun enable(context: Context) = setEnabledSetting(context, true)
fun disable(context: Context) = setEnabledSetting(context, false)
private fun setEnabledSetting(context: Context, enabled: Boolean) {
context.packageManager.setComponentEnabledSetting(
context.componentName,
if (enabled) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
fun isEnabled(context: Context): Boolean =
context.packageManager.getComponentEnabledSetting(context.componentName) ==
COMPONENT_ENABLED_STATE_ENABLED
private val Context.componentName
get() = ComponentName(
this,
StartupTracingConfigStoreIsEnabledGate::class.java.name
)
}
}
internal object StartupTracingConfigStore {
private const val KEY_IS_PERSISTENT = "isPersistent"
private const val KEY_LIB_FILE_PATH = "libtracingPerfettoFilePath"
private const val STARTUP_CONFIG_FILE_NAME = "libtracing_perfetto_startup.properties"
private fun startupConfigFileForPackageName(packageName: String): File =
File("/sdcard/Android/media/$packageName/$STARTUP_CONFIG_FILE_NAME")
/** Loads the config */
fun load(context: Context): StartupTracingConfig? {
// use the fast-check-gate value
if (!StartupTracingConfigStoreIsEnabledGate.isEnabled(context)) return null
// read the config from file
val propertiesFile = startupConfigFileForPackageName(context.packageName)
if (!propertiesFile.exists()) return null
val properties = Properties()
propertiesFile.reader().use { properties.load(it) }
return StartupTracingConfig(
properties.getProperty(KEY_LIB_FILE_PATH),
properties.getProperty(KEY_IS_PERSISTENT).toBoolean()
)
}
/** Stores the config */
fun StartupTracingConfig.store(context: Context) {
startupConfigFileForPackageName(context.packageName)
.bufferedWriter()
.use { writer ->
Properties().also {
it.setProperty(KEY_LIB_FILE_PATH, libFilePath)
it.setProperty(KEY_IS_PERSISTENT, isPersistent.toString())
}.store(writer, null)
}
StartupTracingConfigStoreIsEnabledGate.enable(context) // update the fast-check-gate value
}
/** Deletes the config */
fun clear(context: Context) {
StartupTracingConfigStoreIsEnabledGate.disable(context) // update the fast-check-gate value
startupConfigFileForPackageName(context.packageName).delete()
}
}