Skip to content

Commit e6f6788

Browse files
authored
Merge branch 'main' into feat/tombstone_native_sdk_merge
2 parents df83d57 + 48fbb69 commit e6f6788

27 files changed

Lines changed: 1215 additions & 787 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44

55
### Features
66

7+
- Add `installGroupsOverride` parameter and `installGroups` property to Build Distribution SDK ([#5062](https://github.com/getsentry/sentry-java/pull/5062))
78
- Update Android targetSdk to API 36 (Android 16) ([#5016](https://github.com/getsentry/sentry-java/pull/5016))
89
- Merge Tombstone and Native SDK events into single crash event. ([#5037](https://github.com/getsentry/sentry-java/pull/5037))
910

11+
### Fixes
12+
13+
- Fix scroll target detection for Jetpack Compose ([#5017](https://github.com/getsentry/sentry-java/pull/5017))
14+
1015
### Internal
1116

1217
- Establish new native exception mechanisms to differentiate events generated by `sentry-native` from `ApplicationExitInfo`. ([#5052](https://github.com/getsentry/sentry-java/pull/5052))

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
99

1010
# AndroidX required by AGP >= 3.6.x
1111
android.useAndroidX=true
12+
android.experimental.lint.version=8.9.0
1213

1314
# Release information
1415
versionName=8.31.0

gradle/libs.versions.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ androidx-annotation = { module = "androidx.annotation:annotation", version = "1.
8282
androidx-activity-compose = { module = "androidx.activity:activity-compose", version = "1.8.2" }
8383
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidxCompose" }
8484
androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "androidxCompose" }
85-
androidx-compose-material3 = { module = "androidx.compose.material3:material3", version = "1.2.1" }
85+
androidx-compose-material3 = { module = "androidx.compose.material3:material3", version = "1.4.0" }
86+
androidx-compose-material-icons-core = { module = "androidx.compose.material:material-icons-core", version="1.7.8" }
87+
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version="1.7.8" }
8688
androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidxCompose" }
8789
# Note: don't change without testing forwards compatibility
8890
androidx-compose-ui-replay = { module = "androidx.compose.ui:ui", version = "1.5.0" }

sentry-android-distribution/src/main/java/io/sentry/android/distribution/DistributionHttpClient.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ internal class DistributionHttpClient(private val options: SentryOptions) {
2727
val versionCode: Long,
2828
val versionName: String,
2929
val buildConfiguration: String,
30+
val installGroupsOverride: List<String>? = null,
3031
)
3132

3233
/**
@@ -58,6 +59,9 @@ internal class DistributionHttpClient(private val options: SentryOptions) {
5859
append("&build_number=${URLEncoder.encode(params.versionCode.toString(), "UTF-8")}")
5960
append("&build_version=${URLEncoder.encode(params.versionName, "UTF-8")}")
6061
append("&build_configuration=${URLEncoder.encode(params.buildConfiguration, "UTF-8")}")
62+
params.installGroupsOverride?.forEach { group ->
63+
append("&install_groups=${URLEncoder.encode(group, "UTF-8")}")
64+
}
6165
}
6266
val url = URL(urlString)
6367

sentry-android-distribution/src/main/java/io/sentry/android/distribution/UpdateResponseParser.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ internal class UpdateResponseParser(private val options: SentryOptions) {
5757
val downloadUrl = json.optString("download_url", "")
5858
val appName = json.optString("app_name", "")
5959
val createdDate = json.optString("created_date", "")
60+
val installGroups = parseInstallGroups(json)
6061

6162
// Validate required fields (optString returns "null" for null values)
6263
val missingFields = mutableListOf<String>()
@@ -77,6 +78,26 @@ internal class UpdateResponseParser(private val options: SentryOptions) {
7778
)
7879
}
7980

80-
return UpdateInfo(id, buildVersion, buildNumber, downloadUrl, appName, createdDate)
81+
return UpdateInfo(
82+
id,
83+
buildVersion,
84+
buildNumber,
85+
downloadUrl,
86+
appName,
87+
createdDate,
88+
installGroups,
89+
)
90+
}
91+
92+
private fun parseInstallGroups(json: JSONObject): List<String>? {
93+
val installGroupsArray = json.optJSONArray("install_groups") ?: return null
94+
val installGroups = mutableListOf<String>()
95+
for (i in 0 until installGroupsArray.length()) {
96+
val group = installGroupsArray.optString(i)
97+
if (group.isNotEmpty() && group != "null") {
98+
installGroups.add(group)
99+
}
100+
}
101+
return if (installGroups.isEmpty()) null else installGroups
81102
}
82103
}

sentry-android-distribution/src/test/java/io/sentry/android/distribution/UpdateResponseParserTest.kt

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class UpdateResponseParserTest {
3232
"build_number": 42,
3333
"download_url": "https://example.com/download",
3434
"app_name": "Test App",
35-
"created_date": "2023-10-01T00:00:00Z"
35+
"created_date": "2023-10-01T00:00:00Z",
36+
"install_groups": ["beta", "internal"]
3637
},
3738
"current": null
3839
}
@@ -49,6 +50,7 @@ class UpdateResponseParserTest {
4950
assertEquals("https://example.com/download", updateInfo.downloadUrl)
5051
assertEquals("Test App", updateInfo.appName)
5152
assertEquals("2023-10-01T00:00:00Z", updateInfo.createdDate)
53+
assertEquals(listOf("beta", "internal"), updateInfo.installGroups)
5254
}
5355

5456
@Test
@@ -355,4 +357,103 @@ class UpdateResponseParserTest {
355357
error.message.contains("Missing required fields in API response: id"),
356358
)
357359
}
360+
361+
@Test
362+
fun `parseResponse returns null installGroups when not present`() {
363+
val responseBody =
364+
"""
365+
{
366+
"update": {
367+
"id": "update-123",
368+
"build_version": "2.0.0",
369+
"build_number": 42,
370+
"download_url": "https://example.com/download",
371+
"app_name": "Test App",
372+
"created_date": "2023-10-01T00:00:00Z"
373+
}
374+
}
375+
"""
376+
.trimIndent()
377+
378+
val result = parser.parseResponse(200, responseBody)
379+
380+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
381+
val updateInfo = (result as UpdateStatus.NewRelease).info
382+
assertEquals(null, updateInfo.installGroups)
383+
}
384+
385+
@Test
386+
fun `parseResponse returns null installGroups when array is empty`() {
387+
val responseBody =
388+
"""
389+
{
390+
"update": {
391+
"id": "update-123",
392+
"build_version": "2.0.0",
393+
"build_number": 42,
394+
"download_url": "https://example.com/download",
395+
"app_name": "Test App",
396+
"created_date": "2023-10-01T00:00:00Z",
397+
"install_groups": []
398+
}
399+
}
400+
"""
401+
.trimIndent()
402+
403+
val result = parser.parseResponse(200, responseBody)
404+
405+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
406+
val updateInfo = (result as UpdateStatus.NewRelease).info
407+
assertEquals(null, updateInfo.installGroups)
408+
}
409+
410+
@Test
411+
fun `parseResponse returns null installGroups when array is null`() {
412+
val responseBody =
413+
"""
414+
{
415+
"update": {
416+
"id": "update-123",
417+
"build_version": "2.0.0",
418+
"build_number": 42,
419+
"download_url": "https://example.com/download",
420+
"app_name": "Test App",
421+
"created_date": "2023-10-01T00:00:00Z",
422+
"install_groups": null
423+
}
424+
}
425+
"""
426+
.trimIndent()
427+
428+
val result = parser.parseResponse(200, responseBody)
429+
430+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
431+
val updateInfo = (result as UpdateStatus.NewRelease).info
432+
assertEquals(null, updateInfo.installGroups)
433+
}
434+
435+
@Test
436+
fun `parseResponse returns single installGroup`() {
437+
val responseBody =
438+
"""
439+
{
440+
"update": {
441+
"id": "update-123",
442+
"build_version": "2.0.0",
443+
"build_number": 42,
444+
"download_url": "https://example.com/download",
445+
"app_name": "Test App",
446+
"created_date": "2023-10-01T00:00:00Z",
447+
"install_groups": ["beta-testers"]
448+
}
449+
}
450+
"""
451+
.trimIndent()
452+
453+
val result = parser.parseResponse(200, responseBody)
454+
455+
assertTrue("Should return NewRelease", result is UpdateStatus.NewRelease)
456+
val updateInfo = (result as UpdateStatus.NewRelease).info
457+
assertEquals(listOf("beta-testers"), updateInfo.installGroups)
458+
}
358459
}

sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import java.lang.ref.WeakReference
2222
import java.util.concurrent.atomic.AtomicBoolean
2323
import kotlin.math.roundToInt
2424

25-
@SuppressLint("UseKtx")
25+
@SuppressLint("UseKtx", "UseRequiresApi")
2626
@TargetApi(26)
2727
internal class ScreenshotRecorder(
2828
val config: ScreenshotRecorderConfig,

sentry-android-replay/src/main/java/io/sentry/android/replay/WindowRecorder.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.android.replay
22

3+
import android.annotation.SuppressLint
34
import android.annotation.TargetApi
45
import android.graphics.Point
56
import android.os.Handler
@@ -19,6 +20,7 @@ import java.lang.ref.WeakReference
1920
import java.util.concurrent.ScheduledExecutorService
2021
import java.util.concurrent.atomic.AtomicBoolean
2122

23+
@SuppressLint("UseRequiresApi")
2224
@TargetApi(26)
2325
internal class WindowRecorder(
2426
private val options: SentryOptions,

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.android.replay.capture
22

3+
import android.annotation.SuppressLint
34
import android.annotation.TargetApi
45
import android.view.MotionEvent
56
import io.sentry.Breadcrumb
@@ -42,6 +43,7 @@ import java.util.concurrent.atomic.AtomicReference
4243
import kotlin.properties.ReadWriteProperty
4344
import kotlin.reflect.KProperty
4445

46+
@SuppressLint("UseRequiresApi")
4547
@TargetApi(26)
4648
internal abstract class BaseCaptureStrategy(
4749
private val options: SentryOptions,

sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.android.replay.capture
22

3+
import android.annotation.SuppressLint
34
import android.annotation.TargetApi
45
import android.graphics.Bitmap
56
import android.view.MotionEvent
@@ -24,6 +25,7 @@ import java.io.File
2425
import java.util.Date
2526
import java.util.concurrent.ScheduledExecutorService
2627

28+
@SuppressLint("UseRequiresApi")
2729
@TargetApi(26)
2830
internal class BufferCaptureStrategy(
2931
private val options: SentryOptions,

0 commit comments

Comments
 (0)