diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PerfettoProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/PerfettoProfiler.java index d09c725269..6cc9bf7286 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PerfettoProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/PerfettoProfiler.java @@ -2,6 +2,8 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; @@ -41,9 +43,13 @@ public class PerfettoProfiler { private static final long RESULT_TIMEOUT_MS = 5000; + private static final String PROFILING_PACKAGE_NAME = "com.google.android.profiling"; + private static final long EMPTY_TRACE_PROFILING_PACKAGE_VERSION = 370546200L; + private final @NotNull ILogger logger; private final @NotNull ISentryExecutorService executorService; private final @Nullable ProfilingManager profilingManager; + private final long profilingPackageVersion; private final @NotNull CancellationSignal cancellationSignal = new CancellationSignal(); private final @NotNull Object profilingResultLock = new Object(); @@ -60,16 +66,26 @@ public PerfettoProfiler( this( logger, executorService, - (ProfilingManager) context.getSystemService(Context.PROFILING_SERVICE)); + (ProfilingManager) context.getSystemService(Context.PROFILING_SERVICE), + getProfilingPackageVersion(context, logger)); } PerfettoProfiler( final @NotNull ILogger logger, final @NotNull ISentryExecutorService executorService, final @Nullable ProfilingManager profilingManager) { + this(logger, executorService, profilingManager, 0L); + } + + PerfettoProfiler( + final @NotNull ILogger logger, + final @NotNull ISentryExecutorService executorService, + final @Nullable ProfilingManager profilingManager, + final long profilingPackageVersion) { this.logger = logger; this.executorService = executorService; this.profilingManager = profilingManager; + this.profilingPackageVersion = profilingPackageVersion; } public boolean start(final long durationMs) { @@ -84,6 +100,13 @@ public boolean start(final long durationMs) { return false; } + if (profilingPackageVersion == EMPTY_TRACE_PROFILING_PACKAGE_VERSION) { + logger.log( + SentryLevel.WARNING, + "Profiling is not supported by the installed Android profiling package version."); + return false; + } + final Bundle params = new Bundle(); params.putInt(KEY_DURATION_MS, (int) durationMs); params.putInt(KEY_FREQUENCY_HZ, PROFILING_FREQUENCY_HZ); @@ -216,6 +239,20 @@ private void deleteTraceFile(final @Nullable File traceFile) { return traceFile; } + private static long getProfilingPackageVersion( + final @NotNull Context context, final @NotNull ILogger logger) { + try { + final @NotNull PackageInfo packageInfo = + context + .getPackageManager() + .getPackageInfo(PROFILING_PACKAGE_NAME, PackageManager.MATCH_APEX); + return packageInfo.getLongVersionCode(); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + logger.log(SentryLevel.DEBUG, "Failed to resolve Android profiling package version.", e); + return 0L; + } + } + private static @NotNull String errorCodeToString(final int errorCode) { switch (errorCode) { case ProfilingResult.ERROR_FAILED_RATE_LIMIT_PROCESS: diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/PerfettoProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/PerfettoProfilerTest.kt index 0746d36dff..e11f63e79f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/PerfettoProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/PerfettoProfilerTest.kt @@ -23,6 +23,8 @@ import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.robolectric.annotation.Config @@ -52,8 +54,11 @@ class PerfettoProfilerTest { context = ApplicationProvider.getApplicationContext() } - private fun getSut(profilingManager: ProfilingManager? = mockProfilingManager): PerfettoProfiler { - return PerfettoProfiler(mockLogger, executor, profilingManager) + private fun getSut( + profilingManager: ProfilingManager? = mockProfilingManager, + profilingPackageVersion: Long = 0L, + ): PerfettoProfiler { + return PerfettoProfiler(mockLogger, executor, profilingManager, profilingPackageVersion) } private fun createTraceFile(): File { @@ -94,6 +99,22 @@ class PerfettoProfilerTest { assertFalse(profiler.start(60000)) } + @Test + fun `start returns false and does not request profiling for unsupported package version`() { + val profiler = getSut(profilingPackageVersion = 370546200L) + + assertFalse(profiler.start(60000)) + verify(mockProfilingManager, never()).requestProfiling(any(), any(), any(), any(), any(), any()) + } + + @Test + fun `start requests profiling for other package versions`() { + val profiler = getSut(profilingPackageVersion = 370546201L) + + assertTrue(profiler.start(60000)) + verify(mockProfilingManager).requestProfiling(any(), any(), any(), any(), any(), any()) + } + @Test fun `endAndCollect calls listener with null when never started`() { val profiler = getSut()