From 53da7b4ca26b5580236a67952e07da84e1655a99 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 30 Oct 2026 13:09:13 +0100 Subject: [PATCH] ref(android): [minSdk 26 2] Remove code made obsolete by minSdk 26 Remove the API 26 and lower annotations, the runtime checks for API levels below 26 and the branches they guarded, and the bundled ISRG root CA socket factory that only applied to API 25 and lower. Turn the ObsoleteSdkInt lint check back on. Public methods that hybrid SDKs call keep their BuildInfoProvider parameters. Refs JAVA-547 Co-Authored-By: Claude Opus 5.5 --- sentry-android-core/build.gradle.kts | 2 - .../core/AndroidContinuousProfiler.java | 11 - .../core/AndroidOptionsInitializer.java | 9 - .../core/AndroidTransactionProfiler.java | 9 - .../core/NetworkBreadcrumbsIntegration.java | 6 - .../core/SentryRootCaSslSocketFactory.java | 211 ------------------ .../android/core/SentryRootCertificates.java | 65 ------ .../util/AndroidConnectionStatusProvider.java | 109 ++------- .../CellularNetworkTechnologyProvider.java | 4 - .../internal/util/FirstDrawDoneListener.java | 37 +-- .../core/internal/util/ScreenshotUtils.java | 93 +++----- .../util/SentryFrameMetricsCollector.java | 20 +- .../core/performance/AppStartMetrics.java | 31 +-- .../core/AndroidContinuousProfilerTest.kt | 11 - .../core/AndroidTransactionProfilerTest.kt | 11 - .../core/NetworkBreadcrumbsIntegrationTest.kt | 12 - .../core/SentryRootCaSslSocketFactoryTest.kt | 191 ---------------- .../CellularNetworkTechnologyProviderTest.kt | 8 - .../util/FirstDrawDoneListenerTest.kt | 30 +-- .../util/SentryFrameMetricsCollectorTest.kt | 42 +--- sentry-android-ndk/build.gradle.kts | 2 - sentry-android-replay/build.gradle.kts | 2 - .../android/replay/ReplayIntegration.kt | 6 - .../android/replay/ScreenshotRecorder.kt | 2 - .../sentry/android/replay/WindowRecorder.kt | 2 - .../replay/capture/BaseCaptureStrategy.kt | 2 - .../replay/capture/BufferCaptureStrategy.kt | 2 - .../replay/screenshot/CanvasStrategy.kt | 10 - .../sentry/android/replay/util/Persistable.kt | 2 - .../replay/video/SimpleVideoEncoder.kt | 3 - .../viewhierarchy/ComposeViewHierarchyNode.kt | 2 - .../replay/viewhierarchy/ViewHierarchyNode.kt | 2 - 32 files changed, 70 insertions(+), 879 deletions(-) delete mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCaSslSocketFactory.java delete mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCertificates.java delete mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/SentryRootCaSslSocketFactoryTest.kt diff --git a/sentry-android-core/build.gradle.kts b/sentry-android-core/build.gradle.kts index 511fd41d4c0..0f791a46019 100644 --- a/sentry-android-core/build.gradle.kts +++ b/sentry-android-core/build.gradle.kts @@ -55,8 +55,6 @@ android { lint { warningsAsErrors = true checkDependencies = true - // TODO(JAVA-547): Re-enable once the SDK_INT checks made obsolete by minSdk 26 are removed. - disable += "ObsoleteSdkInt" // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds = false diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index 20fea39a28d..0d3296dbe93 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -4,7 +4,6 @@ import static io.sentry.IConnectionStatusProvider.ConnectionStatus.DISCONNECTED; import static java.util.concurrent.TimeUnit.SECONDS; -import android.os.Build; import io.sentry.CompositePerformanceCollector; import io.sentry.DataCategory; import io.sentry.IContinuousProfiler; @@ -174,10 +173,6 @@ private void initScopes() { private void start() { initScopes(); - // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler - // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; - // Let's initialize trace folder and profiling interval init(); // init() didn't create profiler, should never happen @@ -279,12 +274,6 @@ private void stop(final boolean restartProfiler) { return; } - // onTransactionStart() is only available since Lollipop_MR1 - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) { - return; - } - List performanceCollectionData = null; if (performanceCollector != null) { performanceCollectionData = performanceCollector.stop(chunkId.toString()); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 83f9d9b3492..4e0b7440763 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -202,15 +202,6 @@ static void initializeIntegrationsAndProcessors( if (options.getTransportGate() instanceof NoOpTransportGate) { options.setTransportGate(new AndroidTransportGate(options)); } - // API 25 and lower may lack ISRG Root X1 (Let's Encrypt), so the SDK's own envelope uploads to - // Sentry SaaS trust the bundled root in addition to the system ones. Self-hosted DSNs keep the - // platform default, so Network Security Config domain-config trust anchors still apply there. - if (buildInfoProvider.getSdkInfoVersion() <= Build.VERSION_CODES.N_MR1 - && options.getSslSocketFactory() == null - && SentryRootCaSslSocketFactory.isSentrySaasDsn(options.getDsn())) { - options.setSslSocketFactory(new SentryRootCaSslSocketFactory(options.getLogger())); - } - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); options.setAppStartExtender(appStartMetrics.getAppStartExtension()); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java index e44ed746a08..f6e828de83d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java @@ -151,10 +151,6 @@ private void init() { @Override public void start() { - // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler - // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; - // When the first transaction is starting, we can start profiling if (!isRunning.getAndSet(true)) { // Let's initialize trace folder and profiling interval @@ -234,11 +230,6 @@ public void bindTransaction(final @NotNull ITransaction transaction) { final @Nullable List performanceCollectionData, final @NotNull SentryOptions options) { - // onTransactionStart() is only available since Lollipop_MR1 - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - // and SUPPORTED_ABIS since KITKAT - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null; - // check if profiler was created if (profiler == null) { return null; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java index c7f3182b97d..0151c96de67 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java @@ -64,12 +64,6 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions if (androidOptions.isEnableNetworkEventBreadcrumbs()) { - // The specific error is logged in the ConnectivityChecker method - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - options.getLogger().log(SentryLevel.DEBUG, "NetworkCallbacks need Android N+."); - return; - } - try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { networkCallback = new NetworkBreadcrumbsNetworkCallback( diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCaSslSocketFactory.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCaSslSocketFactory.java deleted file mode 100644 index 00e4ec17696..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCaSslSocketFactory.java +++ /dev/null @@ -1,211 +0,0 @@ -package io.sentry.android.core; - -import io.sentry.DsnUtil; -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.util.LazyEvaluator; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Locale; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; -import org.jetbrains.annotations.VisibleForTesting; - -/** - * An {@link SSLSocketFactory} that trusts the system root CAs plus the root CAs bundled in {@link - * SentryRootCertificates}. Both are put into a single trust store which is handed to the platform's - * default {@link TrustManagerFactory}, so certificate validation itself is still done by the - * platform. - * - *

Android API 25 and lower may not ship ISRG Root X1, so TLS handshakes with Let's Encrypt - * certificates fail on those devices. This factory is only set as {@link - * io.sentry.SentryOptions#setSslSocketFactory(SSLSocketFactory)} on those API levels and only when - * the DSN points to Sentry SaaS ({@code sentry.io}), see {@link #isSentrySaasDsn(String)}. So it - * only applies to the SDK's own envelope uploads to Sentry and not to any other connection of the - * app. - * - *

On API 24 and 25, the system CAs are read from the Network Security Config's base config. Any - * {@code domain-config} trust anchors or pins do not apply to connections using this factory. This - * is why it's limited to Sentry SaaS: self-hosted Sentry with a private CA keeps using the platform - * default. - * - *

The underlying {@link SSLContext} is created lazily on first use, so the cost of parsing the - * certificates is paid on the transport thread instead of during {@code SentryAndroid.init}. If - * creating it fails for any reason, the platform default {@link SSLSocketFactory} is used and the - * failure is not retried. - */ -@ApiStatus.Internal -final class SentryRootCaSslSocketFactory extends SSLSocketFactory { - - static final @NotNull String SENTRY_ROOT_CA_ALIAS_PREFIX = "sentry-root-ca-"; - - private final @NotNull LazyEvaluator delegate; - - /** Provides the CAs trusted by the platform. */ - interface SystemTrustedIssuersProvider { - @NotNull - X509Certificate[] getSystemTrustedIssuers() throws GeneralSecurityException; - } - - SentryRootCaSslSocketFactory(final @NotNull ILogger logger) { - this(logger, SentryRootCaSslSocketFactory::getSystemTrustedIssuers); - } - - @VisibleForTesting - SentryRootCaSslSocketFactory( - final @NotNull ILogger logger, - final @NotNull SystemTrustedIssuersProvider systemTrustedIssuersProvider) { - this.delegate = new LazyEvaluator<>(() -> createDelegate(logger, systemTrustedIssuersProvider)); - } - - /** - * Returns {@code true} if the DSN's host is {@code sentry.io} or a subdomain of it, for example - * {@code o123.ingest.us.sentry.io}. Those are the hosts whose certificates chain to the bundled - * roots. The host is read with {@link DsnUtil#getHost(String)}, which parses it the same way as - * the DSN the SDK sends envelopes to. - */ - static boolean isSentrySaasDsn(final @Nullable String dsn) { - @Nullable String host = DsnUtil.getHost(dsn); - if (host == null) { - return false; - } - if (host.endsWith(".")) { - host = host.substring(0, host.length() - 1); - } - host = host.toLowerCase(Locale.ROOT); - return host.equals("sentry.io") || host.endsWith(".sentry.io"); - } - - private static @NotNull SSLSocketFactory createDelegate( - final @NotNull ILogger logger, - final @NotNull SystemTrustedIssuersProvider systemTrustedIssuersProvider) { - try { - final @NotNull TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init( - createTrustStore(systemTrustedIssuersProvider.getSystemTrustedIssuers())); - - final @NotNull SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustManagerFactory.getTrustManagers(), null); - return sslContext.getSocketFactory(); - } catch (Exception e) { - // Catches Exception, not only GeneralSecurityException and IOException: some OEM builds - // throw RuntimeExceptions from their trust store or certificate code. An exception escaping - // here would make every envelope upload fail without being retried, and the LazyEvaluator - // would try to build the delegate again on every send. Errors such as OutOfMemoryError are - // not caught on purpose, the process can't recover from them. - logger.log( - SentryLevel.ERROR, - "Failed to create SSLSocketFactory with bundled Sentry root CAs, using the default one.", - e); - return HttpsURLConnection.getDefaultSSLSocketFactory(); - } - } - - /** Returns the CAs trusted by the platform's default trust manager. */ - private static @NotNull X509Certificate[] getSystemTrustedIssuers() - throws GeneralSecurityException { - final @NotNull TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - for (final TrustManager trustManager : trustManagerFactory.getTrustManagers()) { - if (trustManager instanceof X509TrustManager) { - return ((X509TrustManager) trustManager).getAcceptedIssuers(); - } - } - throw new GeneralSecurityException("No default X509TrustManager available"); - } - - /** - * Creates a trust store containing the given system trusted CAs plus the bundled Sentry root CAs. - */ - @VisibleForTesting - static @NotNull KeyStore createTrustStore(final @NotNull X509Certificate[] systemTrustedIssuers) - throws GeneralSecurityException, IOException { - final @NotNull KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, null); - for (int i = 0; i < systemTrustedIssuers.length; i++) { - keyStore.setCertificateEntry("system-ca-" + i, systemTrustedIssuers[i]); - } - final @NotNull CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - for (int i = 0; i < SentryRootCertificates.ALL.length; i++) { - final @NotNull Certificate certificate = - certificateFactory.generateCertificate( - new ByteArrayInputStream( - SentryRootCertificates.ALL[i].getBytes(StandardCharsets.UTF_8))); - keyStore.setCertificateEntry(SENTRY_ROOT_CA_ALIAS_PREFIX + i, certificate); - } - return keyStore; - } - - @TestOnly - @NotNull - SSLSocketFactory getDelegate() { - return delegate.getValue(); - } - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getValue().getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getValue().getSupportedCipherSuites(); - } - - @Override - public Socket createSocket() throws IOException { - return delegate.getValue().createSocket(); - } - - @Override - public Socket createSocket( - final Socket socket, final String host, final int port, final boolean autoClose) - throws IOException { - return delegate.getValue().createSocket(socket, host, port, autoClose); - } - - @Override - public Socket createSocket(final String host, final int port) throws IOException { - return delegate.getValue().createSocket(host, port); - } - - @Override - public Socket createSocket( - final String host, final int port, final InetAddress localHost, final int localPort) - throws IOException { - return delegate.getValue().createSocket(host, port, localHost, localPort); - } - - @Override - public Socket createSocket(final InetAddress host, final int port) throws IOException { - return delegate.getValue().createSocket(host, port); - } - - @Override - public Socket createSocket( - final InetAddress address, - final int port, - final InetAddress localAddress, - final int localPort) - throws IOException { - return delegate.getValue().createSocket(address, port, localAddress, localPort); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCertificates.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCertificates.java deleted file mode 100644 index f6a53189adb..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryRootCertificates.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.sentry.android.core; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * Root CA certificates the SDK bundles for its own envelope uploads, see {@link - * SentryRootCaSslSocketFactory}. - * - *

Sentry SaaS uses Let's Encrypt, whose default certificate chains all end at ISRG Root X1 (see - * https://docs.sentry.io/security-legal-pii/security/ssl/ and - * https://letsencrypt.org/certificates/). ISRG Root X1 was only added to the Android CA store in - * Android 7.1 (API 25), so older devices can't validate those chains on their own. It's also - * applied on API 25 to cover vendor builds that lack it. The other Sentry roots (DigiCert, and - * Google Trust Services via its GlobalSign cross-sign) are already trusted on all supported API - * levels. - */ -@ApiStatus.Internal -final class SentryRootCertificates { - - private SentryRootCertificates() {} - - /** - * ISRG Root X1, SHA-256 - * 96:BC:EC:06:26:49:76:F3:74:60:77:9A:CF:28:C5:A7:CF:E8:A3:C0:AA:E1:1A:8F:FC:EE:05:C0:BD:DF:08:C6 - */ - static final @NotNull String ISRG_ROOT_X1 = - "-----BEGIN CERTIFICATE-----\n" - + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" - + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" - + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" - + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" - + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" - + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" - + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" - + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" - + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" - + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" - + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" - + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" - + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" - + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" - + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" - + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" - + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" - + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" - + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" - + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" - + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" - + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" - + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" - + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" - + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" - + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" - + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" - + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" - + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" - + "-----END CERTIFICATE-----\n"; - - // TODO(2028-01-28): Google Trust Services certs are only trusted on API <= 28 via the GTS - // Root R1 cross-sign by GlobalSign Root CA, which expires on 2028-01-28. Before that date, - // bundle GTS Root R1-R4 here and raise the API gate in AndroidOptionsInitializer to <= 28. - // See https://github.com/getsentry/sentry-java/pull/6227 - static final @NotNull String[] ALL = new String[] {ISRG_ROOT_X1}; -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java index 62a212e6dcc..2ad28a563e1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java @@ -7,10 +7,8 @@ import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; -import android.os.Build; import android.os.Handler; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import io.sentry.IConnectionStatusProvider; import io.sentry.ILogger; import io.sentry.ISentryLifecycleToken; @@ -99,9 +97,7 @@ public AndroidConnectionStatusProvider( new CellularNetworkTechnologyProvider(this.context, options.getLogger(), buildInfoProvider); capabilities[0] = NetworkCapabilities.NET_CAPABILITY_INTERNET; - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) { - capabilities[1] = NetworkCapabilities.NET_CAPABILITY_VALIDATED; - } + capabilities[1] = NetworkCapabilities.NET_CAPABILITY_VALIDATED; // Register network callback immediately for caching //noinspection Convert2MethodRef @@ -123,13 +119,9 @@ private boolean isNetworkEffectivelyConnected( } // Check for general internet capability AND validated status - boolean hasInternetAndValidated = - networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) { - hasInternetAndValidated = - hasInternetAndValidated - && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); - } + final boolean hasInternetAndValidated = + networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); if (!hasInternetAndValidated) { return false; @@ -223,7 +215,6 @@ public void onAvailable(final @NotNull Network network) { } } - @RequiresApi(Build.VERSION_CODES.O) @Override public void onUnavailable() { clearCacheAndNotifyObservers(); @@ -393,12 +384,6 @@ private void updateCache(@Nullable NetworkCapabilities networkCapabilities) { return; } - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.M) { - cachedNetworkCapabilities = null; - cacheFreshUntil = Deadline.after(ticker, CACHE_TTL_MINUTES, TimeUnit.MINUTES); - return; - } - // Fallback: query current active network final ConnectivityManager connectivityManager = getConnectivityManager(context, options.getLogger()); @@ -630,65 +615,25 @@ public NetworkCapabilities getCachedNetworkCapabilities() { boolean wifi = false; boolean cellular = false; - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.M) { - - final Network activeNetwork = connectivityManager.getActiveNetwork(); - if (activeNetwork == null) { - logger.log(SentryLevel.INFO, "Network is null and cannot check network status"); - return null; - } - final NetworkCapabilities networkCapabilities = - connectivityManager.getNetworkCapabilities(activeNetwork); - if (networkCapabilities == null) { - logger.log(SentryLevel.INFO, "NetworkCapabilities is null and cannot check network type"); - return null; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - ethernet = true; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - wifi = true; - } - if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - cellular = true; - } - } else { - // ideally using connectivityManager.getAllNetworks(), but its >= Android L only - - // for some reason linting didn't allow a single @SuppressWarnings("deprecation") on method - // signature - @SuppressWarnings("deprecation") - final android.net.NetworkInfo activeNetworkInfo = - connectivityManager.getActiveNetworkInfo(); - - if (activeNetworkInfo == null) { - logger.log(SentryLevel.INFO, "NetworkInfo is null, there's no active network."); - return null; - } - - @SuppressWarnings("deprecation") - final int type = activeNetworkInfo.getType(); - - @SuppressWarnings("deprecation") - final int TYPE_ETHERNET = ConnectivityManager.TYPE_ETHERNET; - - @SuppressWarnings("deprecation") - final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI; - - @SuppressWarnings("deprecation") - final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE; - - switch (type) { - case TYPE_ETHERNET: - ethernet = true; - break; - case TYPE_WIFI: - wifi = true; - break; - case TYPE_MOBILE: - cellular = true; - break; - } + final Network activeNetwork = connectivityManager.getActiveNetwork(); + if (activeNetwork == null) { + logger.log(SentryLevel.INFO, "Network is null and cannot check network status"); + return null; + } + final NetworkCapabilities networkCapabilities = + connectivityManager.getNetworkCapabilities(activeNetwork); + if (networkCapabilities == null) { + logger.log(SentryLevel.INFO, "NetworkCapabilities is null and cannot check network type"); + return null; + } + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + ethernet = true; + } + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + wifi = true; + } + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + cellular = true; } // TODO: change the protocol to be a list of transports as a device may have the capability of @@ -758,10 +703,6 @@ public static boolean addNetworkCallback( final @NotNull ILogger logger, final @NotNull BuildInfoProvider buildInfoProvider, final @NotNull NetworkCallback networkCallback) { - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - logger.log(SentryLevel.DEBUG, "NetworkCallbacks need Android N+."); - return false; - } if (!Permissions.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { logger.log(SentryLevel.INFO, "No permission (ACCESS_NETWORK_STATE) to check network status."); @@ -787,10 +728,6 @@ static boolean registerNetworkCallback( final @NotNull BuildInfoProvider buildInfoProvider, final @Nullable Handler handler, final @NotNull NetworkCallback networkCallback) { - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - logger.log(SentryLevel.DEBUG, "NetworkCallbacks need Android N+."); - return false; - } final ConnectivityManager connectivityManager = getConnectivityManager(context, logger); if (connectivityManager == null) { return false; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProvider.java index a5468ae913f..5cff72f68fd 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProvider.java @@ -144,10 +144,6 @@ public void unregister() { @SuppressLint({"MissingPermission", "InlinedApi", "NewApi"}) private @Nullable String getDataNetworkTechnology() { - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - // getDataNetworkType was added in API 24. - return null; - } // The SDK doesn't declare these permissions, so the technology is only available to apps that // already request one of them. READ_BASIC_PHONE_STATE only exists from API 31 on, where the // display info listener is used instead, but checking it is harmless. diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java index 533739d51d8..bed670b3d2c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/FirstDrawDoneListener.java @@ -19,7 +19,6 @@ package io.sentry.android.core.internal.util; import android.app.Activity; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.view.View; @@ -69,32 +68,13 @@ public static void registerForNextDraw( } /** Registers a post-draw callback for the next draw of a view. */ + // The BuildInfoProvider param is kept for compatibility, as hybrid SDKs call this method. public static void registerForNextDraw( final @NotNull View view, final @NotNull Runnable drawDoneCallback, final @NotNull BuildInfoProvider buildInfoProvider) { final FirstDrawDoneListener listener = new FirstDrawDoneListener(view, drawDoneCallback); - // Handle bug prior to API 26 where OnDrawListener from the floating ViewTreeObserver is not - // merged into the real ViewTreeObserver. - // https://android.googlesource.com/platform/frameworks/base/+/9f8ec54244a5e0343b9748db3329733f259604f3 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.O - && !isAliveAndAttached(view)) { - view.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) { - view.getViewTreeObserver().addOnDrawListener(listener); - view.removeOnAttachStateChangeListener(this); - } - - @Override - public void onViewDetachedFromWindow(View view) { - view.removeOnAttachStateChangeListener(this); - } - }); - } else { - view.getViewTreeObserver().addOnDrawListener(listener); - } + view.getViewTreeObserver().addOnDrawListener(listener); } private FirstDrawDoneListener(final @NotNull View view, final @NotNull Runnable callback) { @@ -120,17 +100,4 @@ public void onDraw() { }); mainThreadHandler.postAtFrontOfQueue(callback); } - - /** - * Helper to avoid bug - * prior to API 26, where the floating ViewTreeObserver's OnDrawListeners are not merged into - * the real ViewTreeObserver during attach. - * - * @return true if the View is already attached and the ViewTreeObserver is not a floating - * placeholder. - */ - private static boolean isAliveAndAttached(final @NotNull View view) { - return view.getViewTreeObserver().isAlive() && view.isAttachedToWindow(); - } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java index 657ba54cbd2..2d4ebeb07ff 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java @@ -3,8 +3,6 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.view.PixelCopy; @@ -122,8 +120,8 @@ public class ScreenshotUtils { final @NotNull IThreadChecker threadChecker, final @NotNull ILogger logger, final @NotNull BuildInfoProvider buildInfoProvider) { - // We are keeping BuildInfoProvider param for compatibility, as it's being used by - // cross-platform SDKs + // We are keeping the IThreadChecker and BuildInfoProvider params for compatibility, as they are + // being used by cross-platform SDKs if (!isActivityValid(activity)) { logger.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot."); @@ -159,63 +157,38 @@ public class ScreenshotUtils { final @NotNull CountDownLatch latch = new CountDownLatch(1); - // Use Pixel Copy API on new devices, fallback to canvas rendering on older ones - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.O) { - - final HandlerThread thread = new HandlerThread("SentryScreenshot"); - thread.start(); - - boolean success = false; - final AtomicInteger copyResultCode = new AtomicInteger(-1); - try { - final Handler handler = new Handler(thread.getLooper()); - - PixelCopy.request( - window, - bitmap, - copyResult -> { - copyResultCode.set(copyResult); - latch.countDown(); - }, - handler); - - final boolean completed = latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS); - success = completed && copyResultCode.get() == PixelCopy.SUCCESS; - } catch (Throwable e) { - // ignored - logger.log(SentryLevel.ERROR, "Taking screenshot using PixelCopy failed.", e); - } finally { - thread.quit(); - } - - if (!success) { - logger.log( - SentryLevel.WARNING, - "PixelCopy failed for screenshot capture (result=%d).", - copyResultCode.get()); - return null; - } - } else { - final Canvas canvas = new Canvas(bitmap); - if (threadChecker.isMainThread()) { - view.draw(canvas); - latch.countDown(); - } else { - activity.runOnUiThread( - () -> { - try { - view.draw(canvas); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e); - } finally { - latch.countDown(); - } - }); - } + final HandlerThread thread = new HandlerThread("SentryScreenshot"); + thread.start(); + + boolean success = false; + final AtomicInteger copyResultCode = new AtomicInteger(-1); + try { + final Handler handler = new Handler(thread.getLooper()); + + PixelCopy.request( + window, + bitmap, + copyResult -> { + copyResultCode.set(copyResult); + latch.countDown(); + }, + handler); + + final boolean completed = latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS); + success = completed && copyResultCode.get() == PixelCopy.SUCCESS; + } catch (Throwable e) { + // ignored + logger.log(SentryLevel.ERROR, "Taking screenshot using PixelCopy failed.", e); + } finally { + thread.quit(); + } - if (!latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - return null; - } + if (!success) { + logger.log( + SentryLevel.WARNING, + "PixelCopy failed for screenshot capture (result=%d).", + copyResultCode.get()); + return null; } return bitmap; } catch (Throwable e) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java index 2c0ae246558..1c643f36bf2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java @@ -12,7 +12,6 @@ import android.view.Choreographer; import android.view.FrameMetrics; import android.view.Window; -import androidx.annotation.RequiresApi; import io.sentry.ILogger; import io.sentry.ISentryLifecycleToken; import io.sentry.SentryLevel; @@ -110,10 +109,6 @@ public SentryFrameMetricsCollector( if (!(appContext instanceof Application)) { return; } - // FrameMetrics api is only available since sdk version N - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.N) { - return; - } isAvailable = true; // The frame metrics HandlerThread is started lazily on the first startCollection() call. @@ -222,25 +217,14 @@ public static boolean isSlow(long frameDuration, final long expectedFrameDuratio return frameDuration > expectedFrameDuration; } - /** - * Return the frame start timestamp. On API 26+, this value is read directly from {@link - * FrameMetrics}; older APIs use the reflected Choreographer timestamp. - */ - @SuppressLint("NewApi") private long getFrameStartTimestamp(final @NotNull FrameMetrics frameMetrics) { - - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.O) { - return frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP); - } - - return getLastKnownFrameStartTimeNanos(); + return frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP); } /** * Return time spent on the main thread (cpu) for frame creation. It doesn't consider time spent * on the render thread (gpu). */ - @RequiresApi(api = Build.VERSION_CODES.N) private long getFrameCpuDuration(final @NotNull FrameMetrics frameMetrics) { // Inspired by JankStats // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi24Impl.kt;l=74-79;drc=1de6215c6bd9e887e3d94556e9ac55cfb7b8c797 @@ -512,7 +496,6 @@ void onFrameMetricCollected( @ApiStatus.Internal public interface WindowFrameMetricsManager { - @RequiresApi(api = Build.VERSION_CODES.N) default void addOnFrameMetricsAvailableListener( final @NotNull Window window, final @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener, @@ -523,7 +506,6 @@ default void addOnFrameMetricsAvailableListener( window.addOnFrameMetricsAvailableListener(frameMetricsAvailableListener, handler); } - @RequiresApi(api = Build.VERSION_CODES.N) default void removeOnFrameMetricsAvailableListener( final @NotNull Window window, final @Nullable Window.OnFrameMetricsAvailableListener frameMetricsAvailableListener) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index dacab11e4f4..e42ecaf1c21 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -554,28 +554,15 @@ private void scheduleHeadlessAppStartCheckOnMain() { if (!headlessAppStartCheckPending.compareAndSet(false, true)) { return; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Looper.getMainLooper() - .getQueue() - .addIdleHandler( - () -> { - firstIdle = SystemClock.uptimeMillis(); - headlessAppStartCheckPending.set(false); - handleHeadlessAppStartIfNeededOnMain(); - return false; - }); - } else { - final Handler handler = new Handler(Looper.getMainLooper()); - handler.post( - () -> { - firstIdle = SystemClock.uptimeMillis(); - handler.post( - () -> { - headlessAppStartCheckPending.set(false); - handleHeadlessAppStartIfNeededOnMain(); - }); - }); - } + Looper.getMainLooper() + .getQueue() + .addIdleHandler( + () -> { + firstIdle = SystemClock.uptimeMillis(); + headlessAppStartCheckPending.set(false); + handleHeadlessAppStartIfNeededOnMain(); + return false; + }); } /** diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index caaa30152a4..806f00ee8b1 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -293,17 +293,6 @@ class AndroidContinuousProfilerTest { assertEquals(0, profiler.rootSpanCounter) } - @Test - fun `profiler works only on api 22+`() { - val buildInfo = - mock { - whenever(it.sdkInfoVersion).thenReturn(Build.VERSION_CODES.LOLLIPOP) - } - val profiler = fixture.getSut(buildInfo) - profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler) - assertFalse(profiler.isRunning) - } - @Test fun `profiler evaluates profilingTracesDirPath options only on first start`() { val profiler = fixture.getSut { it.cacheDirPath = null } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt index b37a6bbdee5..fe3f2cffdf3 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt @@ -243,17 +243,6 @@ class AndroidTransactionProfilerTest { assertEquals(profilingTraceData.transactionId, fixture.transaction1.eventId.toString()) } - @Test - fun `profiler works only on api 22+`() { - val buildInfo = - mock { - whenever(it.sdkInfoVersion).thenReturn(Build.VERSION_CODES.LOLLIPOP) - } - val profiler = fixture.getSut(context, buildInfo) - profiler.start() - assertFalse(profiler.isRunning) - } - @Test fun `profiler on profilesSampleRate=0 false`() { fixture.options.apply { profilesSampleRate = 0.0 } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt index 711f5f7fe0b..8085189b029 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/NetworkBreadcrumbsIntegrationTest.kt @@ -94,18 +94,6 @@ class NetworkBreadcrumbsIntegrationTest { assertNull(sut.networkCallback) } - @Test - fun `It doesn't register callback if not on Android N+`() { - val buildInfo = mock() - whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.M) - val sut = fixture.getSut(buildInfo = buildInfo) - - sut.register(fixture.scopes, fixture.options) - - assertTrue(AndroidConnectionStatusProvider.getChildCallbacks().isEmpty()) - assertNull(sut.networkCallback) - } - @Test fun `When NetworkBreadcrumbsIntegration is closed, it should unregister the callback`() { val sut = fixture.getSut() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryRootCaSslSocketFactoryTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryRootCaSslSocketFactoryTest.kt deleted file mode 100644 index e55eb5508fc..00000000000 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryRootCaSslSocketFactoryTest.kt +++ /dev/null @@ -1,191 +0,0 @@ -package io.sentry.android.core - -import com.google.common.truth.Truth.assertThat -import io.sentry.ILogger -import io.sentry.SentryLevel -import java.security.KeyStore -import java.security.MessageDigest -import java.security.cert.CertPathValidator -import java.security.cert.CertPathValidatorException -import java.security.cert.Certificate -import java.security.cert.CertificateFactory -import java.security.cert.PKIXParameters -import java.security.cert.X509Certificate -import java.util.Date -import java.util.concurrent.atomic.AtomicInteger -import javax.net.ssl.HttpsURLConnection -import javax.net.ssl.TrustManagerFactory -import javax.net.ssl.X509TrustManager -import kotlin.test.Test -import kotlin.test.assertFailsWith -import org.mockito.kotlin.any -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify - -class SentryRootCaSslSocketFactoryTest { - - @Test - fun `bundles ISRG Root X1 matching the fingerprint published on docs_sentry_io`() { - val keyStore = SentryRootCaSslSocketFactory.createTrustStore(emptyArray()) - - val fingerprints = - keyStore.aliases().toList().map { alias -> keyStore.getCertificate(alias).sha256() } - - assertThat(fingerprints) - .containsExactly( - // ISRG Root X1 - ISRG_ROOT_X1_SHA256 - ) - } - - @Test - fun `trust store keeps the system trusted CAs and adds the bundled root`() { - // The JVM's cacerts already has the ISRG roots, so remove them to act like an old Android - // device. - val systemIssuers = systemIssuersWithoutIsrgRoots() - assertThat(systemIssuers).isNotEmpty() - - val trustManager = - defaultTrustManager(SentryRootCaSslSocketFactory.createTrustStore(systemIssuers)) - - val accepted = trustManager.acceptedIssuers.map { it.sha256() } - assertThat(accepted).containsAtLeastElementsIn(systemIssuers.map { it.sha256() }) - assertThat(accepted).contains(ISRG_ROOT_X1_SHA256) - } - - @Test - fun `Let's Encrypt chain only validates with the bundled root`() { - val chain = letsEncryptChain() - - // Without any ISRG root, like Android 7.1.0 and older. - val ex = - assertFailsWith { - validate(chain, plainKeyStore(systemIssuersWithoutIsrgRoots())) - } - assertThat(ex.message).contains("trust anchor") - - // The same system CAs plus the bundled root. - validate(chain, SentryRootCaSslSocketFactory.createTrustStore(systemIssuersWithoutIsrgRoots())) - // Only the bundled root. - validate(chain, SentryRootCaSslSocketFactory.createTrustStore(emptyArray())) - } - - @Test - fun `falls back to the default SSLSocketFactory once when building the delegate throws a RuntimeException`() { - val logger = mock() - val calls = AtomicInteger() - val sut = - SentryRootCaSslSocketFactory(logger) { - calls.incrementAndGet() - throw IllegalStateException("broken OEM trust store") - } - - assertThat(sut.delegate).isSameInstanceAs(HttpsURLConnection.getDefaultSSLSocketFactory()) - assertThat(sut.supportedCipherSuites).isNotEmpty() - assertThat(sut.delegate).isSameInstanceAs(HttpsURLConnection.getDefaultSSLSocketFactory()) - - assertThat(calls.get()).isEqualTo(1) - verify(logger).log(eq(SentryLevel.ERROR), any(), any()) - } - - @Test - fun `creates a custom delegate SSLSocketFactory lazily and only once`() { - val sut = SentryRootCaSslSocketFactory(mock()) - - val delegate = sut.delegate - - assertThat(delegate).isNotSameInstanceAs(HttpsURLConnection.getDefaultSSLSocketFactory()) - assertThat(sut.supportedCipherSuites).isNotEmpty() - assertThat(sut.delegate).isSameInstanceAs(delegate) - } - - @Test - fun `isSentrySaasDsn matches sentry_io and its subdomains only`() { - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@o1.ingest.us.sentry.io/1")) - .isTrue() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@o1.ingest.sentry.io/1")) - .isTrue() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key:secret@sentry.io:443/1")) - .isTrue() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@O1.Ingest.SENTRY.IO./1")) - .isTrue() - - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn(null)).isFalse() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("")).isFalse() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@sentry.example.com/1")) - .isFalse() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@notsentry.io/1")).isFalse() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@sentry.io.evil.com/1")) - .isFalse() - assertThat(SentryRootCaSslSocketFactory.isSentrySaasDsn("https://key@evil.com/sentry.io")) - .isFalse() - } - - /** The JVM's system CAs without ISRG Root X1 and any other ISRG root, such as Root YR. */ - private fun systemIssuersWithoutIsrgRoots(): Array = - defaultTrustManager(null) - .acceptedIssuers - .filter { - val subject = it.subjectX500Principal.name - it.sha256() != ISRG_ROOT_X1_SHA256 && - !subject.contains("O=ISRG") && - !subject.contains("O=Internet Security Research Group") - } - .toTypedArray() - - private fun plainKeyStore(certificates: Array): KeyStore = - KeyStore.getInstance(KeyStore.getDefaultType()).apply { - load(null, null) - certificates.forEachIndexed { i, certificate -> setCertificateEntry("ca-$i", certificate) } - } - - /** - * A real chain served by valid-isrgrootx1.letsencrypt.org: leaf, YR1 intermediate and Root YR - * cross-signed by ISRG Root X1. - */ - private fun letsEncryptChain(): List { - val factory = CertificateFactory.getInstance("X.509") - return listOf( - "letsencrypt-leaf.pem", - "letsencrypt-yr1.pem", - "letsencrypt-root-yr-cross-signed-by-isrg-root-x1.pem", - ) - .map { name -> - javaClass.classLoader!!.getResourceAsStream("root-ca/$name").use { - factory.generateCertificate(it) as X509Certificate - } - } - } - - /** - * Validates the chain with PKIX. Validates at a fixed date within the leaf's validity, so the - * test doesn't break when the leaf certificate expires. - */ - private fun validate(chain: List, trustStore: KeyStore) { - val params = - PKIXParameters(trustStore).apply { - isRevocationEnabled = false - date = Date(VALIDATION_DATE_MILLIS) - } - CertPathValidator.getInstance("PKIX") - .validate(CertificateFactory.getInstance("X.509").generateCertPath(chain), params) - } - - private fun defaultTrustManager(keyStore: KeyStore?): X509TrustManager { - val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) - factory.init(keyStore) - return factory.trustManagers.filterIsInstance().first() - } - - private fun Certificate.sha256(): String = - MessageDigest.getInstance("SHA-256").digest(encoded).joinToString(":") { "%02X".format(it) } - - companion object { - private const val ISRG_ROOT_X1_SHA256 = - "96:BC:EC:06:26:49:76:F3:74:60:77:9A:CF:28:C5:A7:CF:E8:A3:C0:AA:E1:1A:8F:FC:EE:05:C0:BD:DF:08:C6" - - // 2026-10-14T00:00:00Z, within the validity of all certificates in the Let's Encrypt chain. - private const val VALIDATION_DATE_MILLIS = 1791936000000L - } -} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProviderTest.kt index 4bf1d7de427..29047ee304e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/CellularNetworkTechnologyProviderTest.kt @@ -335,14 +335,6 @@ class CellularNetworkTechnologyProviderTest { assertThat(provider.cellularNetworkTechnology).isNull() } - @Test - fun `below Android 7 the technology is not read because getDataNetworkType is unavailable`() { - val provider = fixture.getSut(sdkVersion = Build.VERSION_CODES.M, hasPermission = true) - - assertThat(provider.cellularNetworkTechnology).isNull() - verify(fixture.telephonyManager, never()).dataNetworkType - } - @Test fun `below Android 12 registering does nothing`() { val provider = fixture.getSut(sdkVersion = Build.VERSION_CODES.R) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/FirstDrawDoneListenerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/FirstDrawDoneListenerTest.kt index 57e72ac4d72..edae2fefe3d 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/FirstDrawDoneListenerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/FirstDrawDoneListenerTest.kt @@ -12,7 +12,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.android.core.BuildInfoProvider import io.sentry.test.getProperty -import java.util.concurrent.CopyOnWriteArrayList import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertIs @@ -55,34 +54,7 @@ class FirstDrawDoneListenerTest { private val fixture = Fixture() @Test - fun `registerForNextDraw adds listener on attach state changed on sdk 25-`() { - val view = fixture.getSut(Build.VERSION_CODES.N_MR1) - - // OnDrawListener is not registered, it is delayed for later - FirstDrawDoneListener.registerForNextDraw(view, {}, fixture.buildInfo) - assertTrue(fixture.onDrawListeners.isEmpty()) - - // Register listener after the view is attached to a window - val listenerInfo = Class.forName("android.view.View\$ListenerInfo") - val mListenerInfo: Any = view.getProperty("mListenerInfo") - val mOnAttachStateChangeListeners: CopyOnWriteArrayList = - mListenerInfo.getProperty(listenerInfo, "mOnAttachStateChangeListeners") - assertFalse(mOnAttachStateChangeListeners.isEmpty()) - - // Dispatch onViewAttachedToWindow() - for (listener in mOnAttachStateChangeListeners) { - listener.onViewAttachedToWindow(view) - } - - assertFalse(fixture.onDrawListeners.isEmpty()) - assertIs(fixture.onDrawListeners[0]) - - // mOnAttachStateChangeListeners is automatically removed - assertTrue(mOnAttachStateChangeListeners.isEmpty()) - } - - @Test - fun `registerForNextDraw adds listener on sdk 26+`() { + fun `registerForNextDraw adds listener immediately`() { val view = fixture.getSut() // Immediately register an OnDrawListener to ViewTreeObserver diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollectorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollectorTest.kt index 334ce229066..80184c2d643 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollectorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollectorTest.kt @@ -117,15 +117,6 @@ class SentryFrameMetricsCollectorTest { } } - @Test - fun `collector works only on api 24+`() { - val buildInfo = - mock { whenever(it.sdkInfoVersion).thenReturn(Build.VERSION_CODES.M) } - val collector = fixture.getSut(context, buildInfo) - val id = collector.startCollection(mock()) - assertNull(id) - } - @Test fun `collector works only if context is instance of Application`() { val collector = fixture.getSut(mock()) @@ -338,35 +329,6 @@ class SentryFrameMetricsCollectorTest { assertEquals(100, collector.getLastKnownFrameStartTimeNanos()) } - @Test - fun `collector reads frame start from choreographer field under version O`() { - val collector = fixture.getSut(context) - // Execute all posted tasks - Shadows.shadowOf(Looper.getMainLooper()).idle() - val listener = - collector.getProperty("frameMetricsAvailableListener") - val choreographer = collector.getProperty("choreographer") - choreographer.injectForField("mLastFrameTimeNanos", 100) - val frameMetrics = createMockFrameMetrics() - - var timesCalled = 0 - collector.startCollection { - frameStartNanos, - frameEndNanos, - durationNanos, - delayNanos, - isSlow, - isFrozen, - refreshRate -> - // The frame end is 100 (Choreographer.mLastFrameTimeNanos) plus frame duration - assertEquals(100 + durationNanos, frameEndNanos) - timesCalled++ - } - listener.onFrameMetricsAvailable(createMockWindow(), frameMetrics, 0) - // Assert the callback was called - assertEquals(1, timesCalled) - } - @Test fun `collector reads frame start from frameMetrics object on version O+`() { val buildInfo = @@ -611,9 +573,7 @@ class SentryFrameMetricsCollectorTest { @Test fun `getFramesDelay returns -1 when not available`() { - val buildInfo = - mock { whenever(it.sdkInfoVersion).thenReturn(Build.VERSION_CODES.M) } - val collector = fixture.getSut(context, buildInfo) + val collector = fixture.getSut(mock()) val result = collector.getFramesDelay(0, TimeUnit.SECONDS.toNanos(1)) assertEquals(-1.0, result.delaySeconds) diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts index 8b176ccd3be..dff85906ca1 100644 --- a/sentry-android-ndk/build.gradle.kts +++ b/sentry-android-ndk/build.gradle.kts @@ -44,8 +44,6 @@ android { lint { warningsAsErrors = true checkDependencies = true - // TODO(JAVA-547): Re-enable once the SDK_INT checks made obsolete by minSdk 26 are removed. - disable += "ObsoleteSdkInt" // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds = false diff --git a/sentry-android-replay/build.gradle.kts b/sentry-android-replay/build.gradle.kts index 1132a966d74..1a92f9a0b36 100644 --- a/sentry-android-replay/build.gradle.kts +++ b/sentry-android-replay/build.gradle.kts @@ -55,8 +55,6 @@ android { lint { warningsAsErrors = true checkDependencies = true - // TODO(JAVA-547): Re-enable once the SDK_INT checks made obsolete by minSdk 26 are removed. - disable += "ObsoleteSdkInt" // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds = false diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt index 4d6614a7832..bca4f5796a3 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt @@ -3,7 +3,6 @@ package io.sentry.android.replay import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.os.Build import android.view.MotionEvent import io.sentry.Breadcrumb import io.sentry.DataCategory.All @@ -148,11 +147,6 @@ public class ReplayIntegration( override fun register(scopes: IScopes, options: SentryOptions) { this.options = options - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - options.logger.log(INFO, "Session replay is only supported on API 26 and above") - return - } - this.scopes = scopes recorder = recorderProvider?.invoke() diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt index ce987c24ce8..1b354a27f0e 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt @@ -1,7 +1,6 @@ package io.sentry.android.replay import android.annotation.SuppressLint -import android.annotation.TargetApi import android.content.Context import android.graphics.Bitmap import android.view.View @@ -23,7 +22,6 @@ import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.roundToInt @SuppressLint("UseKtx", "UseRequiresApi") -@TargetApi(26) internal class ScreenshotRecorder( val config: ScreenshotRecorderConfig, val options: SentryOptions, diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/WindowRecorder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/WindowRecorder.kt index 19c61900889..c786f1e2a0f 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/WindowRecorder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/WindowRecorder.kt @@ -1,7 +1,6 @@ package io.sentry.android.replay import android.annotation.SuppressLint -import android.annotation.TargetApi import android.graphics.Point import android.os.Handler import android.os.HandlerThread @@ -22,7 +21,6 @@ import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.atomic.AtomicBoolean @SuppressLint("UseRequiresApi") -@TargetApi(26) internal class WindowRecorder( private val options: SentryOptions, private val screenshotRecorderCallback: ScreenshotRecorderCallback? = null, diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt index b6b8981f8ce..05d773aeb6a 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt @@ -1,7 +1,6 @@ package io.sentry.android.replay.capture import android.annotation.SuppressLint -import android.annotation.TargetApi import android.view.MotionEvent import io.sentry.Breadcrumb import io.sentry.DateUtils @@ -50,7 +49,6 @@ import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @SuppressLint("UseRequiresApi") -@TargetApi(26) internal abstract class BaseCaptureStrategy @Suppress("LongParameterList") constructor( diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt index 071acc19cae..81c30ca2540 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt @@ -1,7 +1,6 @@ package io.sentry.android.replay.capture import android.annotation.SuppressLint -import android.annotation.TargetApi import android.graphics.Bitmap import android.view.MotionEvent import io.sentry.DataCategory.All @@ -39,7 +38,6 @@ import java.util.concurrent.ScheduledExecutorService * rate-limited, so the buffer stays warm for when the limit expires. */ @SuppressLint("UseRequiresApi") -@TargetApi(26) internal class BufferCaptureStrategy @Suppress("LongParameterList") constructor( diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/screenshot/CanvasStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/screenshot/CanvasStrategy.kt index 6956782cc27..b3d46108ecb 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/screenshot/CanvasStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/screenshot/CanvasStrategy.kt @@ -440,22 +440,18 @@ private class TextIgnoringDelegateCanvas : Canvas() { return delegate.clipRect(left, top, right, bottom) } - @RequiresApi(Build.VERSION_CODES.O) override fun clipOutRect(rect: RectF): Boolean { return delegate.clipOutRect(rect) } - @RequiresApi(Build.VERSION_CODES.O) override fun clipOutRect(rect: Rect): Boolean { return delegate.clipOutRect(rect) } - @RequiresApi(Build.VERSION_CODES.O) override fun clipOutRect(left: Float, top: Float, right: Float, bottom: Float): Boolean { return delegate.clipOutRect(left, top, right, bottom) } - @RequiresApi(Build.VERSION_CODES.O) override fun clipOutRect(left: Int, top: Int, right: Int, bottom: Int): Boolean { return delegate.clipOutRect(left, top, right, bottom) } @@ -469,7 +465,6 @@ private class TextIgnoringDelegateCanvas : Canvas() { return delegate.clipPath(path) } - @RequiresApi(Build.VERSION_CODES.O) override fun clipOutPath(path: Path): Boolean { return delegate.clipOutPath(path) } @@ -571,7 +566,6 @@ private class TextIgnoringDelegateCanvas : Canvas() { delegate.drawARGB(a, r, g, b) } - @RequiresApi(Build.VERSION_CODES.O) override fun drawBitmap(bitmap: Bitmap, left: Float, top: Float, paint: Paint?) { val sampledColor = sampleBitmapColor(bitmap, paint, null) solidPaint.setColor(sampledColor) @@ -579,7 +573,6 @@ private class TextIgnoringDelegateCanvas : Canvas() { delegate.drawRect(left, top, left + bitmap.width, top + bitmap.height, solidPaint) } - @RequiresApi(Build.VERSION_CODES.O) override fun drawBitmap(bitmap: Bitmap, src: Rect?, dst: RectF, paint: Paint?) { val sampledColor = sampleBitmapColor(bitmap, paint, src) solidPaint.setColor(sampledColor) @@ -587,7 +580,6 @@ private class TextIgnoringDelegateCanvas : Canvas() { delegate.drawRect(dst, solidPaint) } - @RequiresApi(Build.VERSION_CODES.O) override fun drawBitmap(bitmap: Bitmap, src: Rect?, dst: Rect, paint: Paint?) { val sampledColor = sampleBitmapColor(bitmap, paint, src) solidPaint.setColor(sampledColor) @@ -625,7 +617,6 @@ private class TextIgnoringDelegateCanvas : Canvas() { // not supported } - @RequiresApi(Build.VERSION_CODES.O) override fun drawBitmap(bitmap: Bitmap, matrix: Matrix, paint: Paint?) { val sampledColor = sampleBitmapColor(bitmap, paint, null) solidPaint.setColor(sampledColor) @@ -964,7 +955,6 @@ private class TextIgnoringDelegateCanvas : Canvas() { drawMaskedText(paint, x, y) } - @RequiresApi(Build.VERSION_CODES.O) private fun sampleBitmapColor(bitmap: Bitmap, paint: Paint?, src: Rect?): Int { if (bitmap.isRecycled) { return Color.BLACK diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Persistable.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Persistable.kt index 9de0bd3aab5..3de61735c94 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Persistable.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Persistable.kt @@ -1,7 +1,6 @@ package io.sentry.android.replay.util import android.annotation.SuppressLint -import android.annotation.TargetApi import io.sentry.ReplayRecording import io.sentry.SentryOptions import io.sentry.android.replay.ReplayCache @@ -15,7 +14,6 @@ import java.util.concurrent.ScheduledExecutorService // cpu @SuppressLint("UseRequiresApi") @Suppress("unused") -@TargetApi(26) internal class PersistableLinkedList( private val propertyName: String, private val options: SentryOptions, diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt index b1e3cd20513..072147cafcd 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt @@ -28,7 +28,6 @@ package io.sentry.android.replay.video import android.annotation.SuppressLint -import android.annotation.TargetApi import android.graphics.Bitmap import android.media.MediaCodec import android.media.MediaCodecInfo @@ -57,7 +56,6 @@ private const val TIMEOUT_USEC = 100_000L private const val MAX_EOS_STALL_ITERATIONS = 10 @SuppressLint("UseRequiresApi") -@TargetApi(26) internal class SimpleVideoEncoder( val options: SentryOptions, val muxerConfig: MuxerConfig, @@ -350,7 +348,6 @@ internal fun shouldUseSoftwareCanvas(): Boolean = Build.HARDWARE.contains("ranchu", ignoreCase = true) @SuppressLint("UseRequiresApi") -@TargetApi(24) internal data class MuxerConfig( val file: File, var recordingWidth: Int, diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt index 2e40144e2de..ec1ab680f8a 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt @@ -3,7 +3,6 @@ package io.sentry.android.replay.viewhierarchy import android.annotation.SuppressLint -import android.annotation.TargetApi import android.view.View import androidx.compose.ui.graphics.isUnspecified import androidx.compose.ui.graphics.toArgb @@ -36,7 +35,6 @@ import java.lang.ref.WeakReference import java.lang.reflect.Method @SuppressLint("UseRequiresApi") -@TargetApi(26) internal object ComposeViewHierarchyNode { private val getCollapsedSemanticsMethod: Method? by lazy(LazyThreadSafetyMode.NONE) { diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt index e55ba659a8e..8e7ca2d8fe0 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt @@ -1,7 +1,6 @@ package io.sentry.android.replay.viewhierarchy import android.annotation.SuppressLint -import android.annotation.TargetApi import android.graphics.Rect import android.view.SurfaceView import android.view.View @@ -19,7 +18,6 @@ import io.sentry.android.replay.util.totalPaddingTopSafe import java.lang.ref.WeakReference @SuppressLint("UseRequiresApi") -@TargetApi(26) internal sealed class ViewHierarchyNode( val x: Float, val y: Float,