Skip to content

Commit 30edd98

Browse files
authored
Merge 0eebd3b into bc4be3b
2 parents bc4be3b + 0eebd3b commit 30edd98

File tree

3 files changed

+63
-16
lines changed

3 files changed

+63
-16
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
### Features
6+
- Use PixelCopy API for capturing screenshots on API level 24+ ([#3008](https://github.com/getsentry/sentry-java/pull/3008))
7+
58
### Fixes
69

710
- Fix crash when HTTP connection error message contains formatting symbols ([#3002](https://github.com/getsentry/sentry-java/pull/3002))

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

+59-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
import android.graphics.Bitmap;
66
import android.graphics.Canvas;
77
import android.os.Build;
8+
import android.os.Handler;
9+
import android.os.HandlerThread;
10+
import android.view.PixelCopy;
811
import android.view.View;
12+
import android.view.Window;
913
import androidx.annotation.Nullable;
1014
import io.sentry.ILogger;
1115
import io.sentry.SentryLevel;
@@ -14,6 +18,7 @@
1418
import java.io.ByteArrayOutputStream;
1519
import java.util.concurrent.CountDownLatch;
1620
import java.util.concurrent.TimeUnit;
21+
import java.util.concurrent.atomic.AtomicBoolean;
1722
import org.jetbrains.annotations.ApiStatus;
1823
import org.jetbrains.annotations.NotNull;
1924

@@ -38,13 +43,14 @@ public class ScreenshotUtils {
3843

3944
if (!isActivityValid(activity, buildInfoProvider)
4045
|| activity.getWindow() == null
41-
|| activity.getWindow().getDecorView() == null
42-
|| activity.getWindow().getDecorView().getRootView() == null) {
46+
|| activity.getWindow().peekDecorView() == null
47+
|| activity.getWindow().peekDecorView().getRootView() == null) {
4348
logger.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot.");
4449
return null;
4550
}
4651

47-
final View view = activity.getWindow().getDecorView().getRootView();
52+
final Window window = activity.getWindow();
53+
final View view = window.peekDecorView().getRootView();
4854
if (view.getWidth() <= 0 || view.getHeight() <= 0) {
4955
logger.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot.");
5056
return null;
@@ -55,20 +61,57 @@ public class ScreenshotUtils {
5561
final Bitmap bitmap =
5662
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
5763

58-
final Canvas canvas = new Canvas(bitmap);
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);
64+
final @NotNull CountDownLatch latch = new CountDownLatch(1);
65+
66+
// Use Pixel Copy API on new devices, fallback to canvas rendering on older ones
67+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
68+
69+
final HandlerThread thread = new HandlerThread("screenshot");
70+
thread.start();
71+
72+
boolean success = false;
73+
try {
74+
final Handler handler = new Handler(thread.getLooper());
75+
final AtomicBoolean copyResultSuccess = new AtomicBoolean(false);
76+
77+
PixelCopy.request(
78+
window,
79+
bitmap,
80+
copyResult -> {
81+
copyResultSuccess.set(copyResult == PixelCopy.SUCCESS);
6782
latch.countDown();
68-
} catch (Throwable e) {
69-
logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e);
70-
}
71-
});
83+
},
84+
handler);
85+
86+
success =
87+
latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS) && copyResultSuccess.get();
88+
} catch (InterruptedException e) {
89+
// ignored
90+
} finally {
91+
thread.quit();
92+
}
93+
94+
if (!success) {
95+
return null;
96+
}
97+
} else {
98+
final Canvas canvas = new Canvas(bitmap);
99+
if (mainThreadChecker.isMainThread()) {
100+
view.draw(canvas);
101+
latch.countDown();
102+
} else {
103+
activity.runOnUiThread(
104+
() -> {
105+
try {
106+
view.draw(canvas);
107+
} catch (Throwable e) {
108+
logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e);
109+
} finally {
110+
latch.countDown();
111+
}
112+
});
113+
}
114+
72115
if (!latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
73116
return null;
74117
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class ScreenshotEventProcessorTest {
4646
whenever(rootView.height).thenReturn(1)
4747
whenever(view.rootView).thenReturn(rootView)
4848
whenever(window.decorView).thenReturn(view)
49+
whenever(window.peekDecorView()).thenReturn(view)
4950
whenever(activity.window).thenReturn(window)
5051
whenever(activity.runOnUiThread(any())).then {
5152
it.getArgument<Runnable>(0).run()

0 commit comments

Comments
 (0)