TracingReceiver.kt
/*
* Copyright 2022 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.Context
import android.content.Intent
import android.os.Build
import android.util.JsonWriter
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY
import androidx.tracing.perfetto.PerfettoSdkTrace.Response
import androidx.tracing.perfetto.StartupTracingConfigStore.store
import androidx.tracing.perfetto.internal.handshake.protocol.RequestKeys.ACTION_DISABLE_TRACING_COLD_START
import androidx.tracing.perfetto.internal.handshake.protocol.RequestKeys.ACTION_ENABLE_TRACING
import androidx.tracing.perfetto.internal.handshake.protocol.RequestKeys.ACTION_ENABLE_TRACING_COLD_START
import androidx.tracing.perfetto.internal.handshake.protocol.RequestKeys.KEY_PATH
import androidx.tracing.perfetto.internal.handshake.protocol.RequestKeys.KEY_PERSISTENT
import androidx.tracing.perfetto.internal.handshake.protocol.Response
import androidx.tracing.perfetto.internal.handshake.protocol.ResponseKeys
import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_ERROR_OTHER
import androidx.tracing.perfetto.internal.handshake.protocol.ResponseResultCodes.RESULT_CODE_SUCCESS
import java.io.File
import java.io.StringWriter
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
/** Allows for enabling tracing in an app using a broadcast. @see [ACTION_ENABLE_TRACING] */
@RestrictTo(LIBRARY)
class TracingReceiver : BroadcastReceiver() {
private val executor by lazy {
ThreadPoolExecutor(
/* corePoolSize = */ 0,
/* maximumPoolSize = */ 1,
/* keepAliveTime = */ 10, // gives time for tooling to side-load the .so file
/* unit = */ TimeUnit.SECONDS,
/* workQueue = */ LinkedBlockingQueue()
)
}
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || intent.action !in listOf(
ACTION_ENABLE_TRACING,
ACTION_ENABLE_TRACING_COLD_START,
ACTION_DISABLE_TRACING_COLD_START
)
) return
// Path to the provided library binary file (optional). If not provided, local library files
// will be used if present.
val srcPath = intent.extras?.getString(KEY_PATH)
val pendingResult = goAsync()
executor.execute {
try {
val response = when (intent.action) {
ACTION_ENABLE_TRACING -> enableTracingImmediate(srcPath, context)
ACTION_ENABLE_TRACING_COLD_START ->
enableTracingColdStart(
context,
srcPath,
intent.extras?.getString(KEY_PERSISTENT).toBoolean()
)
ACTION_DISABLE_TRACING_COLD_START -> disableTracingColdStart(context)
else -> throw IllegalStateException() // supported actions checked earlier
}
pendingResult.setResult(response.resultCode, response.toJsonString(), null)
} finally {
pendingResult.finish()
}
}
}
/**
* Enables Perfetto SDK tracing in the app
*/
private fun enableTracingImmediate(srcPath: String?, context: Context?): Response =
when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> {
// TODO(234351579): Support API < 30
Response(
RESULT_CODE_ERROR_OTHER,
"SDK version not supported. Current minimum SDK = ${Build.VERSION_CODES.R}"
)
}
srcPath != null && context != null -> {
try {
PerfettoSdkTrace.enable(File(srcPath), context)
} catch (e: Exception) {
Response(RESULT_CODE_ERROR_OTHER, e)
}
}
srcPath != null && context == null -> {
Response(
RESULT_CODE_ERROR_OTHER,
"Cannot copy source file: $srcPath without access to a Context instance."
)
}
else -> {
// Library path was not provided, trying to resolve using app's local library files.
PerfettoSdkTrace.enable()
}
}
/**
* Handles [ACTION_ENABLE_TRACING_COLD_START]
*
* See [ACTION_ENABLE_TRACING_COLD_START] documentation for steps required before and after.
*/
private fun enableTracingColdStart(
context: Context?,
srcPath: String?,
isPersistent: Boolean
): Response = enableTracingImmediate(srcPath, context).also {
if (it.resultCode == RESULT_CODE_SUCCESS) {
val config = StartupTracingConfig(libFilePath = srcPath, isPersistent = isPersistent)
if (context == null) return Response(
RESULT_CODE_ERROR_OTHER,
"Cannot set up cold start tracing without a Context instance."
)
config.store(context)
}
}
private fun disableTracingColdStart(context: Context?): Response = when {
context != null -> {
StartupTracingConfigStore.clear(context)
Response(RESULT_CODE_SUCCESS)
}
else ->
Response(
RESULT_CODE_ERROR_OTHER,
"Cannot ensure we can disable cold start tracing without access to an app Context" +
" instance"
)
}
private fun Response.toJsonString(): String {
val output = StringWriter()
JsonWriter(output).use {
it.beginObject()
it.name(ResponseKeys.KEY_RESULT_CODE)
it.value(resultCode)
it.name(ResponseKeys.KEY_REQUIRED_VERSION)
it.value(requiredVersion)
message?.let { msg ->
it.name(ResponseKeys.KEY_MESSAGE)
it.value(msg)
}
it.endObject()
}
return output.toString()
}
}