5
5
import android .graphics .Bitmap ;
6
6
import android .graphics .Canvas ;
7
7
import android .os .Build ;
8
+ import android .os .Handler ;
9
+ import android .os .HandlerThread ;
10
+ import android .view .PixelCopy ;
8
11
import android .view .View ;
12
+ import android .view .Window ;
13
+
9
14
import androidx .annotation .Nullable ;
10
15
import io .sentry .ILogger ;
11
16
import io .sentry .SentryLevel ;
14
19
import java .io .ByteArrayOutputStream ;
15
20
import java .util .concurrent .CountDownLatch ;
16
21
import java .util .concurrent .TimeUnit ;
22
+ import java .util .concurrent .atomic .AtomicBoolean ;
23
+
17
24
import org .jetbrains .annotations .ApiStatus ;
18
25
import org .jetbrains .annotations .NotNull ;
19
26
@@ -38,13 +45,14 @@ public class ScreenshotUtils {
38
45
39
46
if (!isActivityValid (activity , buildInfoProvider )
40
47
|| activity .getWindow () == null
41
- || activity .getWindow ().getDecorView () == null
42
- || activity .getWindow ().getDecorView ().getRootView () == null ) {
48
+ || activity .getWindow ().peekDecorView () == null
49
+ || activity .getWindow ().peekDecorView ().getRootView () == null ) {
43
50
logger .log (SentryLevel .DEBUG , "Activity isn't valid, not taking screenshot." );
44
51
return null ;
45
52
}
46
53
47
- final View view = activity .getWindow ().getDecorView ().getRootView ();
54
+ final Window window = activity .getWindow ();
55
+ final View view = window .peekDecorView ().getRootView ();
48
56
if (view .getWidth () <= 0 || view .getHeight () <= 0 ) {
49
57
logger .log (SentryLevel .DEBUG , "View's width and height is zeroed, not taking screenshot." );
50
58
return null ;
@@ -55,20 +63,55 @@ public class ScreenshotUtils {
55
63
final Bitmap bitmap =
56
64
Bitmap .createBitmap (view .getWidth (), view .getHeight (), Bitmap .Config .ARGB_8888 );
57
65
58
- final Canvas canvas = new Canvas (bitmap );
59
- if (mainThreadChecker .isMainThread ()) {
60
- view .draw (canvas );
66
+ final @ NotNull CountDownLatch latch = new CountDownLatch (1 );
67
+
68
+ // Use Pixel Copy API on new devices, fallback to canvas rendering on older ones
69
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
70
+
71
+ final HandlerThread thread = new HandlerThread ("screenshot" );
72
+ thread .start ();
73
+
74
+ boolean success = false ;
75
+ try {
76
+ final Handler handler = new Handler (thread .getLooper ());
77
+ final AtomicBoolean copyResultSuccess = new AtomicBoolean (false );
78
+
79
+ PixelCopy .request (
80
+ window ,
81
+ bitmap , copyResult -> {
82
+ copyResultSuccess .set (copyResult == PixelCopy .SUCCESS );
83
+ latch .countDown ();
84
+ },
85
+ handler );
86
+
87
+ success = 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
+ }
61
97
} 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
- });
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
+
72
115
if (!latch .await (CAPTURE_TIMEOUT_MS , TimeUnit .MILLISECONDS )) {
73
116
return null ;
74
117
}
0 commit comments