diff --git a/sentry-android-core/proguard-rules.pro b/sentry-android-core/proguard-rules.pro index b22a4574320..5ebad5ac0c8 100644 --- a/sentry-android-core/proguard-rules.pro +++ b/sentry-android-core/proguard-rules.pro @@ -80,6 +80,6 @@ ##---------------End: proguard configuration for sentry-android-replay ---------- ##---------------Begin: proguard configuration for sentry-android-distribution ---------- --dontwarn io.sentry.android.distribution.internal.DistributionIntegration --keepnames class io.sentry.android.distribution.internal.DistributionIntegration +-dontwarn io.sentry.android.distribution.DistributionIntegration +-keepnames class io.sentry.android.distribution.DistributionIntegration ##---------------End: proguard configuration for sentry-android-distribution ---------- 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 0feed1ca71d..296916bb9ef 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 @@ -32,7 +32,7 @@ import io.sentry.android.core.internal.util.AndroidThreadChecker; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.android.core.performance.AppStartMetrics; -import io.sentry.android.distribution.internal.DistributionIntegration; +import io.sentry.android.distribution.DistributionIntegration; import io.sentry.android.fragment.FragmentLifecycleIntegration; import io.sentry.android.replay.DefaultReplayBreadcrumbConverter; import io.sentry.android.replay.ReplayIntegration; @@ -394,7 +394,9 @@ static void installDefaultIntegrations( options.setReplayController(replay); } if (isDistributionAvailable) { - options.addIntegration(new DistributionIntegration()); + final DistributionIntegration distribution = new DistributionIntegration((context)); + options.setDistributionController(distribution); + options.addIntegration(distribution); } options .getFeedbackOptions() diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index ac025653037..82263ebcbda 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -42,7 +42,7 @@ public final class SentryAndroid { "io.sentry.android.replay.ReplayIntegration"; static final String SENTRY_DISTRIBUTION_INTEGRATION_CLASS_NAME = - "io.sentry.android.distribution.internal.DistributionIntegration"; + "io.sentry.android.distribution.DistributionIntegration"; private static final String TIMBER_CLASS_NAME = "timber.log.Timber"; private static final String FRAGMENT_CLASS_NAME = diff --git a/sentry-android-distribution/api/sentry-android-distribution.api b/sentry-android-distribution/api/sentry-android-distribution.api index a225fa8c287..d9c7ced1cfe 100644 --- a/sentry-android-distribution/api/sentry-android-distribution.api +++ b/sentry-android-distribution/api/sentry-android-distribution.api @@ -1,79 +1,8 @@ -public final class io/sentry/android/distribution/Distribution { - public static final field INSTANCE Lio/sentry/android/distribution/Distribution; - public final fun checkForUpdate (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V - public final fun checkForUpdateBlocking (Landroid/content/Context;)Lio/sentry/android/distribution/UpdateStatus; - public final fun downloadUpdate (Landroid/content/Context;Lio/sentry/android/distribution/UpdateInfo;)V - public final fun init (Landroid/content/Context;)V - public final fun init (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V - public final fun isEnabled ()Z -} - -public final class io/sentry/android/distribution/DistributionOptions { - public fun ()V - public final fun getBuildConfiguration ()Ljava/lang/String; - public final fun getOrgAuthToken ()Ljava/lang/String; - public final fun getOrganizationSlug ()Ljava/lang/String; - public final fun getProjectSlug ()Ljava/lang/String; - public final fun getSentryBaseUrl ()Ljava/lang/String; - public final fun setBuildConfiguration (Ljava/lang/String;)V - public final fun setOrgAuthToken (Ljava/lang/String;)V - public final fun setOrganizationSlug (Ljava/lang/String;)V - public final fun setProjectSlug (Ljava/lang/String;)V - public final fun setSentryBaseUrl (Ljava/lang/String;)V -} - -public final class io/sentry/android/distribution/UpdateInfo { - public fun (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()I - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/android/distribution/UpdateInfo; - public static synthetic fun copy$default (Lio/sentry/android/distribution/UpdateInfo;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/android/distribution/UpdateInfo; - public fun equals (Ljava/lang/Object;)Z - public final fun getAppName ()Ljava/lang/String; - public final fun getBuildNumber ()I - public final fun getBuildVersion ()Ljava/lang/String; - public final fun getCreatedDate ()Ljava/lang/String; - public final fun getDownloadUrl ()Ljava/lang/String; - public final fun getId ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/sentry/android/distribution/UpdateStatus { -} - -public final class io/sentry/android/distribution/UpdateStatus$Error : io/sentry/android/distribution/UpdateStatus { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/android/distribution/UpdateStatus$Error; - public static synthetic fun copy$default (Lio/sentry/android/distribution/UpdateStatus$Error;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/android/distribution/UpdateStatus$Error; - public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/android/distribution/UpdateStatus$NewRelease : io/sentry/android/distribution/UpdateStatus { - public fun (Lio/sentry/android/distribution/UpdateInfo;)V - public final fun component1 ()Lio/sentry/android/distribution/UpdateInfo; - public final fun copy (Lio/sentry/android/distribution/UpdateInfo;)Lio/sentry/android/distribution/UpdateStatus$NewRelease; - public static synthetic fun copy$default (Lio/sentry/android/distribution/UpdateStatus$NewRelease;Lio/sentry/android/distribution/UpdateInfo;ILjava/lang/Object;)Lio/sentry/android/distribution/UpdateStatus$NewRelease; - public fun equals (Ljava/lang/Object;)Z - public final fun getInfo ()Lio/sentry/android/distribution/UpdateInfo; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/android/distribution/UpdateStatus$UpToDate : io/sentry/android/distribution/UpdateStatus { - public static final field INSTANCE Lio/sentry/android/distribution/UpdateStatus$UpToDate; -} - -public final class io/sentry/android/distribution/internal/DistributionIntegration : io/sentry/Integration { - public fun ()V +public final class io/sentry/android/distribution/DistributionIntegration : io/sentry/IDistributionApi, io/sentry/Integration { + public fun (Landroid/content/Context;)V + public fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V + public fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus; + public fun downloadUpdate (Lio/sentry/UpdateInfo;)V public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V } diff --git a/sentry-android-distribution/src/main/AndroidManifest.xml b/sentry-android-distribution/src/main/AndroidManifest.xml deleted file mode 100644 index 9a40236b947..00000000000 --- a/sentry-android-distribution/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/Distribution.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/Distribution.kt deleted file mode 100644 index 85ddcecc96a..00000000000 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/Distribution.kt +++ /dev/null @@ -1,87 +0,0 @@ -package io.sentry.android.distribution - -import android.content.Context -import android.content.Intent -import android.net.Uri -import io.sentry.android.distribution.internal.DistributionInternal - -/** - * The public Android SDK for Sentry Build Distribution. - * - * Provides functionality to check for app updates and download new versions from Sentry's preprod - * artifacts system. - */ -public object Distribution { - /** - * Initialize build distribution with default options. This should be called once per process, - * typically in Application.onCreate(). - * - * @param context Android context - */ - public fun init(context: Context) { - init(context) {} - } - - /** - * Initialize build distribution with the provided configuration. This should be called once per - * process, typically in Application.onCreate(). - * - * @param context Android context - * @param configuration Configuration handler for build distribution options - */ - public fun init(context: Context, configuration: (DistributionOptions) -> Unit) { - val options = DistributionOptions() - configuration(options) - DistributionInternal.init(context, options) - } - - /** - * Check if build distribution is enabled and properly configured. - * - * @return true if build distribution is enabled - */ - public fun isEnabled(): Boolean { - return DistributionInternal.isEnabled() - } - - /** - * Check for available updates synchronously (blocking call). This method will block the calling - * thread while making the network request. Consider using checkForUpdate with callback for - * non-blocking behavior. - * - * @param context Android context - * @return UpdateStatus indicating if an update is available, up to date, or error - */ - public fun checkForUpdateBlocking(context: Context): UpdateStatus { - return DistributionInternal.checkForUpdateBlocking(context) - } - - /** - * Check for available updates asynchronously using a callback. - * - * @param context Android context - * @param onResult Callback that will be called with the UpdateStatus result - */ - public fun checkForUpdate(context: Context, onResult: (UpdateStatus) -> Unit) { - DistributionInternal.checkForUpdateAsync(context, onResult) - } - - /** - * Download and install the provided update by opening the download URL in the default browser or - * appropriate application. - * - * @param context Android context - * @param info Information about the update to download - */ - public fun downloadUpdate(context: Context, info: UpdateInfo) { - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(info.downloadUrl)) - browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - try { - context.startActivity(browserIntent) - } catch (e: android.content.ActivityNotFoundException) { - // No application can handle the HTTP/HTTPS URL, typically no browser installed - // Silently fail as this is expected behavior in some environments - } - } -} diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt new file mode 100644 index 00000000000..53fda5f35db --- /dev/null +++ b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionIntegration.kt @@ -0,0 +1,76 @@ +package io.sentry.android.distribution + +import android.content.Context +import android.content.Intent +import android.net.Uri +import io.sentry.IDistributionApi +import io.sentry.IScopes +import io.sentry.Integration +import io.sentry.SentryOptions +import io.sentry.UpdateInfo +import io.sentry.UpdateStatus + +/** + * The public Android SDK for Sentry Build Distribution. + * + * Provides functionality to check for app updates and download new versions from Sentry's preprod + * artifacts system. + */ +public class DistributionIntegration(context: Context) : Integration, IDistributionApi { + + private lateinit var scopes: IScopes + private lateinit var sentryOptions: SentryOptions + private val context: Context = context.applicationContext + + /** + * Registers the Distribution integration with Sentry. + * + * @param scopes the Scopes + * @param options the options + */ + public override fun register(scopes: IScopes, options: SentryOptions) { + // Store scopes and options for use by distribution functionality + this.scopes = scopes + this.sentryOptions = options + } + + /** + * Check for available updates synchronously (blocking call). This method will block the calling + * thread while making the network request. Consider using checkForUpdate with callback for + * non-blocking behavior. + * + * @return UpdateStatus indicating if an update is available, up to date, or error + */ + public override fun checkForUpdateBlocking(): UpdateStatus { + throw NotImplementedError() + } + + /** + * Check for available updates asynchronously using a callback. + * + * @param onResult Callback that will be called with the UpdateStatus result + */ + public override fun checkForUpdate(onResult: IDistributionApi.UpdateCallback) { + // TODO implement this in a async way + val result = checkForUpdateBlocking() + onResult.onResult(result) + } + + /** + * Download and install the provided update by opening the download URL in the default browser or + * appropriate application. + * + * @param info Information about the update to download + */ + public override fun downloadUpdate(info: UpdateInfo) { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(info.downloadUrl)) + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + try { + context.startActivity(browserIntent) + } catch (e: android.content.ActivityNotFoundException) { + // No application can handle the HTTP/HTTPS URL, typically no browser installed + // Silently fail as this is expected behavior in some environments + } + } +} diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionOptions.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionOptions.kt deleted file mode 100644 index 06fa10c7839..00000000000 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionOptions.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.sentry.android.distribution - -/** Configuration options for Sentry Build Distribution. */ -public class DistributionOptions { - /** Organization authentication token for API access */ - public var orgAuthToken: String = "" - - /** Sentry organization slug */ - public var organizationSlug: String = "" - - /** Sentry project slug */ - public var projectSlug: String = "" - - /** Base URL for Sentry API (defaults to https://sentry.io) */ - public var sentryBaseUrl: String = "https://sentry.io" - - /** Optional build configuration name for filtering (e.g., "debug", "release", "staging") */ - public var buildConfiguration: String? = null -} diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateInfo.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateInfo.kt deleted file mode 100644 index 28277e2d629..00000000000 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateInfo.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.sentry.android.distribution - -/** - * Information about an available app update. - * - * @param id Unique identifier for this build artifact - * @param buildVersion Version string (e.g., "1.2.0") - * @param buildNumber Build number for this version - * @param downloadUrl URL where the update can be downloaded - * @param appName Application name - * @param createdDate ISO timestamp when this build was created - */ -public data class UpdateInfo( - val id: String, - val buildVersion: String, - val buildNumber: Int, - val downloadUrl: String, - val appName: String, - val createdDate: String, -) diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateStatus.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateStatus.kt deleted file mode 100644 index 8549315b7c9..00000000000 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateStatus.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.sentry.android.distribution - -/** Represents the result of checking for app updates. */ -public sealed class UpdateStatus { - /** Current app version is up to date, no update available. */ - public object UpToDate : UpdateStatus() - - /** A new release is available for download. */ - public data class NewRelease(val info: UpdateInfo) : UpdateStatus() - - /** An error occurred during the update check. */ - public data class Error(val message: String) : UpdateStatus() -} diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/internal/DistributionIntegration.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/internal/DistributionIntegration.kt deleted file mode 100644 index 5f3633264c3..00000000000 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/internal/DistributionIntegration.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.sentry.android.distribution.internal - -import io.sentry.IScopes -import io.sentry.Integration -import io.sentry.SentryOptions - -/** - * Integration that automatically enables distribution functionality when the module is included. - */ -public class DistributionIntegration : Integration { - public override fun register(scopes: IScopes, options: SentryOptions) { - // Distribution integration automatically enables when module is present - // No configuration needed - just having this class on the classpath enables the feature - - // If needed, we could initialize DistributionInternal here in the future - // For now, Distribution.init() still needs to be called manually - } -} diff --git a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/internal/DistributionInternal.kt b/sentry-android-distribution/src/main/java/io/sentry/android/distribution/internal/DistributionInternal.kt deleted file mode 100644 index 648d7c70d07..00000000000 --- a/sentry-android-distribution/src/main/java/io/sentry/android/distribution/internal/DistributionInternal.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.sentry.android.distribution.internal - -import android.content.Context -import io.sentry.android.distribution.DistributionOptions -import io.sentry.android.distribution.UpdateStatus - -/** Internal implementation for build distribution functionality. */ -internal object DistributionInternal { - private var isInitialized = false - - @Synchronized - fun init(context: Context, distributionOptions: DistributionOptions) { - // TODO: Implementation will be added in future PR - isInitialized = true - } - - fun isEnabled(): Boolean { - return isInitialized - } - - fun checkForUpdateBlocking(context: Context): UpdateStatus { - return UpdateStatus.Error("Implementation coming in future PR") - } - - fun checkForUpdateAsync(context: Context, onResult: (UpdateStatus) -> Unit) { - throw NotImplementedError() - } -} diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index fa03326cc26..527461870aa 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -767,6 +767,16 @@ public abstract interface class io/sentry/IContinuousProfiler { public abstract fun stopProfiler (Lio/sentry/ProfileLifecycle;)V } +public abstract interface class io/sentry/IDistributionApi { + public abstract fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V + public abstract fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus; + public abstract fun downloadUpdate (Lio/sentry/UpdateInfo;)V +} + +public abstract interface class io/sentry/IDistributionApi$UpdateCallback { + public abstract fun onResult (Lio/sentry/UpdateStatus;)V +} + public abstract interface class io/sentry/IEnvelopeReader { public abstract fun read (Ljava/io/InputStream;)Lio/sentry/SentryEnvelope; } @@ -1469,6 +1479,13 @@ public final class io/sentry/NoOpContinuousProfiler : io/sentry/IContinuousProfi public fun stopProfiler (Lio/sentry/ProfileLifecycle;)V } +public final class io/sentry/NoOpDistributionApi : io/sentry/IDistributionApi { + public fun checkForUpdate (Lio/sentry/IDistributionApi$UpdateCallback;)V + public fun checkForUpdateBlocking ()Lio/sentry/UpdateStatus; + public fun downloadUpdate (Lio/sentry/UpdateInfo;)V + public static fun getInstance ()Lio/sentry/NoOpDistributionApi; +} + public final class io/sentry/NoOpEnvelopeReader : io/sentry/IEnvelopeReader { public static fun getInstance ()Lio/sentry/NoOpEnvelopeReader; public fun read (Ljava/io/InputStream;)Lio/sentry/SentryEnvelope; @@ -2551,7 +2568,7 @@ public final class io/sentry/Sentry { public static fun configureScope (Lio/sentry/ScopeCallback;)V public static fun configureScope (Lio/sentry/ScopeType;Lio/sentry/ScopeCallback;)V public static fun continueTrace (Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext; - public static fun distribution ()Ljava/lang/Object; + public static fun distribution ()Lio/sentry/IDistributionApi; public static fun endSession ()V public static fun flush (J)V public static fun forkedCurrentScope (Ljava/lang/String;)Lio/sentry/IScopes; @@ -3306,6 +3323,8 @@ public class io/sentry/SentryOptions { public fun getDiagnosticLevel ()Lio/sentry/SentryLevel; public fun getDist ()Ljava/lang/String; public fun getDistinctId ()Ljava/lang/String; + public fun getDistribution ()Lio/sentry/SentryOptions$DistributionOptions; + public fun getDistributionController ()Lio/sentry/IDistributionApi; public fun getDsn ()Ljava/lang/String; public fun getEnvelopeDiskCache ()Lio/sentry/cache/IEnvelopeCache; public fun getEnvelopeReader ()Lio/sentry/IEnvelopeReader; @@ -3440,6 +3459,8 @@ public class io/sentry/SentryOptions { public fun setDiagnosticLevel (Lio/sentry/SentryLevel;)V public fun setDist (Ljava/lang/String;)V public fun setDistinctId (Ljava/lang/String;)V + public fun setDistribution (Lio/sentry/SentryOptions$DistributionOptions;)V + public fun setDistributionController (Lio/sentry/IDistributionApi;)V public fun setDsn (Ljava/lang/String;)V public fun setEnableAppStartProfiling (Z)V public fun setEnableAutoSessionTracking (Z)V @@ -3568,6 +3589,15 @@ public final class io/sentry/SentryOptions$Cron { public fun setDefaultTimezone (Ljava/lang/String;)V } +public final class io/sentry/SentryOptions$DistributionOptions { + public field buildConfiguration Ljava/lang/String; + public field orgAuthToken Ljava/lang/String; + public field orgSlug Ljava/lang/String; + public field projectSlug Ljava/lang/String; + public field sentryBaseUrl Ljava/lang/String; + public fun ()V +} + public final class io/sentry/SentryOptions$Logs { public fun ()V public fun getBeforeSend ()Lio/sentry/SentryOptions$Logs$BeforeSendLogCallback; @@ -4295,6 +4325,34 @@ public class io/sentry/UncaughtExceptionHandlerIntegration$UncaughtExceptionHint public fun setFlushable (Lio/sentry/protocol/SentryId;)V } +public final class io/sentry/UpdateInfo { + public fun (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public fun getAppName ()Ljava/lang/String; + public fun getBuildNumber ()I + public fun getBuildVersion ()Ljava/lang/String; + public fun getCreatedDate ()Ljava/lang/String; + public fun getDownloadUrl ()Ljava/lang/String; + public fun getId ()Ljava/lang/String; +} + +public abstract class io/sentry/UpdateStatus { + public fun ()V +} + +public final class io/sentry/UpdateStatus$NewRelease : io/sentry/UpdateStatus { + public fun (Lio/sentry/UpdateInfo;)V + public fun getInfo ()Lio/sentry/UpdateInfo; +} + +public final class io/sentry/UpdateStatus$UpToDate : io/sentry/UpdateStatus { + public static fun getInstance ()Lio/sentry/UpdateStatus$UpToDate; +} + +public final class io/sentry/UpdateStatus$UpdateError : io/sentry/UpdateStatus { + public fun (Ljava/lang/String;)V + public fun getMessage ()Ljava/lang/String; +} + public final class io/sentry/UserFeedback : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun (Lio/sentry/protocol/SentryId;)V public fun (Lio/sentry/protocol/SentryId;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V diff --git a/sentry/src/main/java/io/sentry/IDistributionApi.java b/sentry/src/main/java/io/sentry/IDistributionApi.java new file mode 100644 index 00000000000..ef0ac25d467 --- /dev/null +++ b/sentry/src/main/java/io/sentry/IDistributionApi.java @@ -0,0 +1,42 @@ +package io.sentry; + +import org.jetbrains.annotations.NotNull; + +/** + * API for Sentry Build Distribution functionality. + * + *

Provides methods to check for app updates and download new versions from Sentry's preprod + * artifacts system. + */ +public interface IDistributionApi { + + /** + * Check for available updates synchronously (blocking call). This method will block the calling + * thread while making the network request. Consider using checkForUpdate with callback for + * non-blocking behavior. + * + * @return UpdateStatus indicating if an update is available, up to date, or error + */ + @NotNull + UpdateStatus checkForUpdateBlocking(); + + /** + * Check for available updates asynchronously using a callback. + * + * @param onResult Callback that will be called with the UpdateStatus result + */ + void checkForUpdate(@NotNull UpdateCallback onResult); + + /** + * Download and install the provided update by opening the download URL in the default browser or + * appropriate application. + * + * @param info Information about the update to download + */ + void downloadUpdate(@NotNull UpdateInfo info); + + /** Callback interface for receiving async update check results. */ + interface UpdateCallback { + void onResult(@NotNull UpdateStatus status); + } +} diff --git a/sentry/src/main/java/io/sentry/NoOpDistributionApi.java b/sentry/src/main/java/io/sentry/NoOpDistributionApi.java new file mode 100644 index 00000000000..15470524342 --- /dev/null +++ b/sentry/src/main/java/io/sentry/NoOpDistributionApi.java @@ -0,0 +1,30 @@ +package io.sentry; + +import org.jetbrains.annotations.NotNull; + +/** No-op implementation of IDistributionApi. Used when distribution module is not available. */ +public final class NoOpDistributionApi implements IDistributionApi { + + private static final NoOpDistributionApi instance = new NoOpDistributionApi(); + + private NoOpDistributionApi() {} + + public static NoOpDistributionApi getInstance() { + return instance; + } + + @Override + public @NotNull UpdateStatus checkForUpdateBlocking() { + return UpdateStatus.UpToDate.getInstance(); + } + + @Override + public void checkForUpdate(@NotNull UpdateCallback onResult) { + // No-op implementation - do nothing + } + + @Override + public void downloadUpdate(@NotNull UpdateInfo info) { + // No-op implementation - do nothing + } +} diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 4097cba9342..8c43b83b716 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1307,15 +1307,9 @@ public static IReplayApi replay() { * * @return The distribution API object that provides update checking functionality */ - public static @Nullable Object distribution() { - try { - // Try to get the Distribution object via reflection - Class distributionClass = Class.forName("io.sentry.android.distribution.Distribution"); - return distributionClass.getField("INSTANCE").get(null); - } catch (Exception e) { - // Distribution module not available, return null - return null; - } + @NotNull + public static IDistributionApi distribution() { + return getCurrentScopes().getScope().getOptions().getDistributionController(); } public static void showUserFeedbackDialog() { diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 781e287253b..dee3788bc6c 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -531,6 +531,8 @@ public class SentryOptions { private @NotNull ReplayController replayController = NoOpReplayController.getInstance(); + private @NotNull IDistributionApi distributionController = NoOpDistributionApi.getInstance(); + /** * Controls whether to enable screen tracking. When enabled, the SDK will automatically capture * screen transitions as context for events. @@ -595,6 +597,30 @@ public class SentryOptions { private @NotNull ISocketTagger socketTagger = NoOpSocketTagger.getInstance(); + /** + * Configuration options for Sentry Build Distribution. NOTE: Ideally this would be in + * SentryAndroidOptions, but there's a circular dependency issue between sentry-android-core and + * sentry-android-distribution modules. + */ + public static final class DistributionOptions { + /** Organization authentication token for API access */ + public String orgAuthToken = ""; + + /** Sentry organization slug */ + public String orgSlug = ""; + + /** Sentry project slug */ + public String projectSlug = ""; + + /** Base URL for Sentry API (defaults to https://sentry.io) */ + public String sentryBaseUrl = "https://sentry.io"; + + /** Optional build configuration name for filtering (e.g., "debug", "release", "staging") */ + public @Nullable String buildConfiguration = null; + } + + private @NotNull DistributionOptions distribution = new DistributionOptions(); + /** * Adds an event processor * @@ -2822,6 +2848,15 @@ public void setReplayController(final @Nullable ReplayController replayControlle replayController != null ? replayController : NoOpReplayController.getInstance(); } + public @NotNull IDistributionApi getDistributionController() { + return distributionController; + } + + public void setDistributionController(final @Nullable IDistributionApi distributionController) { + this.distributionController = + distributionController != null ? distributionController : NoOpDistributionApi.getInstance(); + } + @ApiStatus.Experimental public boolean isEnableScreenTracking() { return enableScreenTracking; @@ -3532,6 +3567,14 @@ public interface BeforeSendLogCallback { } } + public @NotNull DistributionOptions getDistribution() { + return distribution; + } + + public void setDistribution(final @NotNull DistributionOptions distribution) { + this.distribution = distribution != null ? distribution : new DistributionOptions(); + } + public enum RequestSize { NONE, SMALL, diff --git a/sentry/src/main/java/io/sentry/UpdateInfo.java b/sentry/src/main/java/io/sentry/UpdateInfo.java new file mode 100644 index 00000000000..95fe8bd7e9c --- /dev/null +++ b/sentry/src/main/java/io/sentry/UpdateInfo.java @@ -0,0 +1,52 @@ +package io.sentry; + +import org.jetbrains.annotations.NotNull; + +/** Information about an available app update. */ +public final class UpdateInfo { + private final @NotNull String id; + private final @NotNull String buildVersion; + private final int buildNumber; + private final @NotNull String downloadUrl; + private final @NotNull String appName; + private final @NotNull String createdDate; + + public UpdateInfo( + final @NotNull String id, + final @NotNull String buildVersion, + final int buildNumber, + final @NotNull String downloadUrl, + final @NotNull String appName, + final @NotNull String createdDate) { + this.id = id; + this.buildVersion = buildVersion; + this.buildNumber = buildNumber; + this.downloadUrl = downloadUrl; + this.appName = appName; + this.createdDate = createdDate; + } + + public @NotNull String getId() { + return id; + } + + public @NotNull String getBuildVersion() { + return buildVersion; + } + + public int getBuildNumber() { + return buildNumber; + } + + public @NotNull String getDownloadUrl() { + return downloadUrl; + } + + public @NotNull String getAppName() { + return appName; + } + + public @NotNull String getCreatedDate() { + return createdDate; + } +} diff --git a/sentry/src/main/java/io/sentry/UpdateStatus.java b/sentry/src/main/java/io/sentry/UpdateStatus.java new file mode 100644 index 00000000000..d2d9ac830d8 --- /dev/null +++ b/sentry/src/main/java/io/sentry/UpdateStatus.java @@ -0,0 +1,44 @@ +package io.sentry; + +import org.jetbrains.annotations.NotNull; + +/** Represents the result of checking for app updates. */ +public abstract class UpdateStatus { + + /** Current app version is up to date, no update available. */ + public static final class UpToDate extends UpdateStatus { + private static final UpToDate INSTANCE = new UpToDate(); + + private UpToDate() {} + + public static UpToDate getInstance() { + return INSTANCE; + } + } + + /** A new release is available for download. */ + public static final class NewRelease extends UpdateStatus { + private final @NotNull UpdateInfo info; + + public NewRelease(final @NotNull UpdateInfo info) { + this.info = info; + } + + public @NotNull UpdateInfo getInfo() { + return info; + } + } + + /** An error occurred during the update check. */ + public static final class UpdateError extends UpdateStatus { + private final @NotNull String message; + + public UpdateError(final @NotNull String message) { + this.message = message; + } + + public @NotNull String getMessage() { + return message; + } + } +}