Skip to content

Commit 504a195

Browse files
committed
Merge branch 'main' into fix/finish-webflux-transaction-before-scope-pop
2 parents bbb1440 + 46b1782 commit 504a195

File tree

28 files changed

+288
-148
lines changed

28 files changed

+288
-148
lines changed

.github/workflows/agp-matrix.yml

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: AGP Matrix Sample Release
1+
name: AGP Matrix Compatibility
22

33
on:
44
push:
@@ -16,29 +16,50 @@ jobs:
1616
with:
1717
access_token: ${{ github.token }}
1818

19-
agp-matrix-sample-release:
20-
runs-on: ubuntu-latest
19+
agp-matrix-compatibility:
20+
timeout-minutes: 25
21+
runs-on: macos-latest
2122
strategy:
2223
fail-fast: false
2324
matrix:
24-
agp: ['8.0.0-rc01','8.1.0-alpha11']
25+
agp: [ '8.0.0','8.1.0-alpha11' ]
26+
ndk: [ true, false ]
2527

26-
name: AGP Matrix Sample Release - AGP ${{ matrix.agp }}
28+
name: AGP Matrix Release - AGP ${{ matrix.agp }} - NDK ${{ matrix.ndk }}
2729
env:
2830
VERSION_AGP: ${{ matrix.agp }}
31+
APPLY_NDK: ${{ matrix.ndk }}
2932

3033
steps:
3134
- name: Checkout Repo
3235
uses: actions/checkout@v3
3336

37+
- name: Setup Gradle
38+
uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 # pin@v2
39+
3440
- name: Setup Java Version
3541
uses: actions/setup-java@v3
3642
with:
3743
distribution: 'temurin'
3844
java-version: '17'
3945

40-
- name: Build the Release variant
41-
uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 # pin@v2
46+
# Clean, build and release a test apk
47+
- name: Make assembleUiTests
48+
run: make assembleUiTests
49+
50+
# We stop gradle at the end to make sure the cache folders
51+
# don't contain any lock files and are free to be cached.
52+
- name: Make stop
53+
run: make stop
54+
55+
- name: Run instrumentation tests
56+
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # pin@v2
4257
with:
43-
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
44-
arguments: sentry-android-integration-tests:sentry-test-agp:assembleRelease
58+
api-level: 30
59+
force-avd-creation: false
60+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
61+
disable-animations: true
62+
disable-spellchecker: true
63+
target: 'aosp_atd'
64+
channel: canary # Necessary for ATDs
65+
script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -DtestBuildType=release --daemon

.github/workflows/integration-tests-ui.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Integration Tests - Ui tests'
1+
name: 'Integration Tests'
22
on:
33
push:
44
branches:
@@ -7,6 +7,14 @@ on:
77
pull_request:
88

99
jobs:
10+
cancel-previous-workflow:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Cancel Previous Runs
14+
uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 # [email protected]
15+
with:
16+
access_token: ${{ github.token }}
17+
1018
test:
1119
name: Ui tests
1220
runs-on: ubuntu-latest
@@ -43,3 +51,4 @@ jobs:
4351
sauce-access-key: ${{ secrets.SAUCE_ACCESS_KEY }}
4452
config-file: .sauce/sentry-uitest-android-ui.yml
4553
if: env.SAUCE_USERNAME != null
54+

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
## Unreleased
44

5+
### Fixes
6+
7+
- Base64 encode internal Apollo3 Headers ([#2707](https://github.com/getsentry/sentry-java/pull/2707))
8+
- Finish WebFlux transaction before popping scope ([#2724](https://github.com/getsentry/sentry-java/pull/2724))
9+
10+
## 6.19.1
11+
12+
### Fixes
13+
14+
- Ensure screenshots and view hierarchies are captured on the main thread ([#2712](https://github.com/getsentry/sentry-java/pull/2712))
15+
16+
## 6.19.0
17+
518
### Features
619

720
- Add Screenshot and ViewHierarchy to integrations list ([#2698](https://github.com/getsentry/sentry-java/pull/2698))
@@ -23,7 +36,8 @@
2336
- Android Profiler on calling thread ([#2691](https://github.com/getsentry/sentry-java/pull/2691))
2437
- Use `configureScope` instead of `withScope` in `Hub.close()`. This ensures that the main scope releases the in-memory data when closing a hub instance. ([#2688](https://github.com/getsentry/sentry-java/pull/2688))
2538
- Remove null keys/values before creating concurrent hashmap in order to avoid NPE ([#2708](https://github.com/getsentry/sentry-java/pull/2708))
26-
- Finish WebFlux transaction before popping scope ([#2724](https://github.com/getsentry/sentry-java/pull/2724))
39+
- Exclude SentryOptions from R8/ProGuard obfuscation ([#2699](https://github.com/getsentry/sentry-java/pull/2699))
40+
- This fixes AGP 8.+ incompatibility, where full R8 mode is enforced
2741

2842
### Dependencies
2943

build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ subprojects {
160160
assignAarTypes()
161161
}
162162

163+
// this is needed for sentry-unity to consume our artifacts locally as proper maven publication
164+
configure<PublishingExtension> {
165+
repositories {
166+
maven {
167+
name = "unityMaven"
168+
url = file("${rootProject.buildDir}/unityMaven").toURI()
169+
}
170+
}
171+
}
172+
163173
// maven central info go to:
164174
// ~/.gradle/gradle.properties
165175

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ android.useAndroidX=true
1010
android.defaults.buildfeatures.buildconfig=true
1111

1212
# Release information
13-
versionName=6.18.1
13+
versionName=6.19.1
1414

1515
# Override the SDK name on native crashes on Android
1616
sentryAndroidSdkName=sentry.native.android

sentry-android-core/api/sentry-android-core.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,9 @@ public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentr
317317
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
318318
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
319319
public static fun snapshotViewHierarchy (Landroid/app/Activity;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy;
320+
public static fun snapshotViewHierarchy (Landroid/app/Activity;Lio/sentry/util/thread/IMainThreadChecker;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy;
320321
public static fun snapshotViewHierarchy (Landroid/view/View;)Lio/sentry/protocol/ViewHierarchy;
321-
public static fun snapshotViewHierarchyAsData (Landroid/app/Activity;Lio/sentry/ISerializer;Lio/sentry/ILogger;)[B
322+
public static fun snapshotViewHierarchyAsData (Landroid/app/Activity;Lio/sentry/util/thread/IMainThreadChecker;Lio/sentry/ISerializer;Lio/sentry/ILogger;)[B
322323
}
323324

324325
public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache {

sentry-android-core/proguard-rules.pro

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@
3232
# Keep Classnames for integrations
3333
-keepnames class * implements io.sentry.IntegrationName
3434

35+
# Keep any custom option classes like SentryAndroidOptions, as they're loaded via reflection
36+
# Also keep method names, as they're e.g. used by native via JNI calls
37+
-keep class * extends io.sentry.SentryOptions { *; }
38+
3539
##---------------End: proguard configuration for android-core ----------

sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public ScreenshotEventProcessor(
5252
return event;
5353
}
5454

55-
final byte[] screenshot = takeScreenshot(activity, options.getLogger(), buildInfoProvider);
55+
final byte[] screenshot =
56+
takeScreenshot(
57+
activity, options.getMainThreadChecker(), options.getLogger(), buildInfoProvider);
5658
if (screenshot == null) {
5759
return event;
5860
}

sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@
1313
import io.sentry.SentryEvent;
1414
import io.sentry.SentryLevel;
1515
import io.sentry.android.core.internal.gestures.ViewUtils;
16+
import io.sentry.android.core.internal.util.AndroidMainThreadChecker;
1617
import io.sentry.protocol.ViewHierarchy;
1718
import io.sentry.protocol.ViewHierarchyNode;
1819
import io.sentry.util.HintUtils;
1920
import io.sentry.util.JsonSerializationUtils;
2021
import io.sentry.util.Objects;
22+
import io.sentry.util.thread.IMainThreadChecker;
2123
import java.util.ArrayList;
2224
import java.util.List;
25+
import java.util.concurrent.CountDownLatch;
26+
import java.util.concurrent.TimeUnit;
27+
import java.util.concurrent.atomic.AtomicReference;
2328
import org.jetbrains.annotations.ApiStatus;
2429
import org.jetbrains.annotations.NotNull;
2530
import org.jetbrains.annotations.Nullable;
@@ -29,6 +34,7 @@
2934
public final class ViewHierarchyEventProcessor implements EventProcessor, IntegrationName {
3035

3136
private final @NotNull SentryAndroidOptions options;
37+
private static final long CAPTURE_TIMEOUT_MS = 1000;
3238

3339
public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options) {
3440
this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required");
@@ -54,7 +60,7 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options)
5460

5561
final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity();
5662
final @Nullable ViewHierarchy viewHierarchy =
57-
snapshotViewHierarchy(activity, options.getLogger());
63+
snapshotViewHierarchy(activity, options.getMainThreadChecker(), options.getLogger());
5864

5965
if (viewHierarchy != null) {
6066
hint.setViewHierarchy(Attachment.fromViewHierarchy(viewHierarchy));
@@ -63,10 +69,13 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options)
6369
return event;
6470
}
6571

66-
@Nullable
6772
public static byte[] snapshotViewHierarchyAsData(
68-
@Nullable Activity activity, @NotNull ISerializer serializer, @NotNull ILogger logger) {
69-
@Nullable ViewHierarchy viewHierarchy = snapshotViewHierarchy(activity, logger);
73+
@Nullable Activity activity,
74+
@NotNull IMainThreadChecker mainThreadChecker,
75+
@NotNull ISerializer serializer,
76+
@NotNull ILogger logger) {
77+
@Nullable
78+
ViewHierarchy viewHierarchy = snapshotViewHierarchy(activity, mainThreadChecker, logger);
7079

7180
if (viewHierarchy == null) {
7281
logger.log(SentryLevel.ERROR, "Could not get ViewHierarchy.");
@@ -90,6 +99,14 @@ public static byte[] snapshotViewHierarchyAsData(
9099
@Nullable
91100
public static ViewHierarchy snapshotViewHierarchy(
92101
@Nullable Activity activity, @NotNull ILogger logger) {
102+
return snapshotViewHierarchy(activity, AndroidMainThreadChecker.getInstance(), logger);
103+
}
104+
105+
@Nullable
106+
public static ViewHierarchy snapshotViewHierarchy(
107+
@Nullable Activity activity,
108+
@NotNull IMainThreadChecker mainThreadChecker,
109+
@NotNull ILogger logger) {
93110
if (activity == null) {
94111
logger.log(SentryLevel.INFO, "Missing activity for view hierarchy snapshot.");
95112
return null;
@@ -108,12 +125,28 @@ public static ViewHierarchy snapshotViewHierarchy(
108125
}
109126

110127
try {
111-
final @NotNull ViewHierarchy viewHierarchy = snapshotViewHierarchy(decorView);
112-
return viewHierarchy;
128+
if (mainThreadChecker.isMainThread()) {
129+
return snapshotViewHierarchy(decorView);
130+
} else {
131+
final CountDownLatch latch = new CountDownLatch(1);
132+
final AtomicReference<ViewHierarchy> viewHierarchy = new AtomicReference<>(null);
133+
activity.runOnUiThread(
134+
() -> {
135+
try {
136+
viewHierarchy.set(snapshotViewHierarchy(decorView));
137+
latch.countDown();
138+
} catch (Throwable t) {
139+
logger.log(SentryLevel.ERROR, "Failed to process view hierarchy.", t);
140+
}
141+
});
142+
if (latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
143+
return viewHierarchy.get();
144+
}
145+
}
113146
} catch (Throwable t) {
114147
logger.log(SentryLevel.ERROR, "Failed to process view hierarchy.", t);
115-
return null;
116148
}
149+
return null;
117150
}
118151

119152
@NotNull

sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,32 @@
1010
import io.sentry.ILogger;
1111
import io.sentry.SentryLevel;
1212
import io.sentry.android.core.BuildInfoProvider;
13+
import io.sentry.util.thread.IMainThreadChecker;
1314
import java.io.ByteArrayOutputStream;
15+
import java.util.concurrent.CountDownLatch;
16+
import java.util.concurrent.TimeUnit;
1417
import org.jetbrains.annotations.ApiStatus;
1518
import org.jetbrains.annotations.NotNull;
1619

1720
@ApiStatus.Internal
1821
public class ScreenshotUtils {
22+
23+
private static final long CAPTURE_TIMEOUT_MS = 1000;
24+
25+
public static @Nullable byte[] takeScreenshot(
26+
final @NotNull Activity activity,
27+
final @NotNull ILogger logger,
28+
final @NotNull BuildInfoProvider buildInfoProvider) {
29+
return takeScreenshot(
30+
activity, AndroidMainThreadChecker.getInstance(), logger, buildInfoProvider);
31+
}
32+
1933
public static @Nullable byte[] takeScreenshot(
2034
final @NotNull Activity activity,
35+
final @NotNull IMainThreadChecker mainThreadChecker,
2136
final @NotNull ILogger logger,
2237
final @NotNull BuildInfoProvider buildInfoProvider) {
38+
2339
if (!isActivityValid(activity, buildInfoProvider)
2440
|| activity.getWindow() == null
2541
|| activity.getWindow().getDecorView() == null
@@ -40,7 +56,23 @@ public class ScreenshotUtils {
4056
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
4157

4258
final Canvas canvas = new Canvas(bitmap);
43-
view.draw(canvas);
59+
if (mainThreadChecker.isMainThread()) {
60+
view.draw(canvas);
61+
} else {
62+
final @NotNull CountDownLatch latch = new CountDownLatch(1);
63+
activity.runOnUiThread(
64+
() -> {
65+
try {
66+
view.draw(canvas);
67+
latch.countDown();
68+
} catch (Throwable e) {
69+
logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e);
70+
}
71+
});
72+
if (!latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
73+
return null;
74+
}
75+
}
4476

4577
// 0 meaning compress for small size, 100 meaning compress for max quality.
4678
// Some formats, like PNG which is lossless, will ignore the quality setting.

sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import io.sentry.MainEventProcessor
1010
import io.sentry.SentryEvent
1111
import io.sentry.SentryIntegrationPackageStorage
1212
import io.sentry.TypeCheckHint.ANDROID_ACTIVITY
13+
import io.sentry.util.thread.IMainThreadChecker
1314
import org.junit.runner.RunWith
15+
import org.mockito.kotlin.any
1416
import org.mockito.kotlin.mock
17+
import org.mockito.kotlin.verify
1518
import org.mockito.kotlin.whenever
1619
import kotlin.test.BeforeTest
1720
import kotlin.test.Test
1821
import kotlin.test.assertEquals
1922
import kotlin.test.assertFalse
23+
import kotlin.test.assertNotNull
2024
import kotlin.test.assertNull
2125
import kotlin.test.assertSame
2226
import kotlin.test.assertTrue
@@ -30,6 +34,7 @@ class ScreenshotEventProcessorTest {
3034
val window = mock<Window>()
3135
val view = mock<View>()
3236
val rootView = mock<View>()
37+
val mainThreadChecker = mock<IMainThreadChecker>()
3338
val options = SentryAndroidOptions().apply {
3439
dsn = "https://[email protected]/proj"
3540
}
@@ -41,10 +46,16 @@ class ScreenshotEventProcessorTest {
4146
whenever(view.rootView).thenReturn(rootView)
4247
whenever(window.decorView).thenReturn(view)
4348
whenever(activity.window).thenReturn(window)
49+
whenever(activity.runOnUiThread(any())).then {
50+
it.getArgument<Runnable>(0).run()
51+
}
52+
53+
whenever(mainThreadChecker.isMainThread).thenReturn(true)
4454
}
4555

4656
fun getSut(attachScreenshot: Boolean = false): ScreenshotEventProcessor {
4757
options.isAttachScreenshot = attachScreenshot
58+
options.mainThreadChecker = mainThreadChecker
4859

4960
return ScreenshotEventProcessor(options, buildInfo)
5061
}
@@ -156,6 +167,20 @@ class ScreenshotEventProcessorTest {
156167
}
157168

158169
@Test
170+
fun `when screenshot event processor is called from background thread it executes on main thread`() {
171+
val sut = fixture.getSut(true)
172+
whenever(fixture.mainThreadChecker.isMainThread).thenReturn(false)
173+
174+
CurrentActivityHolder.getInstance().setActivity(fixture.activity)
175+
176+
val hint = Hint()
177+
val event = fixture.mainProcessor.process(getEvent(), hint)
178+
sut.process(event, hint)
179+
180+
verify(fixture.activity).runOnUiThread(any())
181+
assertNotNull(hint.screenshot)
182+
}
183+
159184
fun `when enabled, the feature is added to the integration list`() {
160185
SentryIntegrationPackageStorage.getInstance().clearStorage()
161186
val hint = Hint()

0 commit comments

Comments
 (0)