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 ;
9
13
import androidx .annotation .Nullable ;
10
14
import io .sentry .ILogger ;
11
15
import io .sentry .SentryLevel ;
14
18
import java .io .ByteArrayOutputStream ;
15
19
import java .util .concurrent .CountDownLatch ;
16
20
import java .util .concurrent .TimeUnit ;
21
+ import java .util .concurrent .atomic .AtomicBoolean ;
17
22
import org .jetbrains .annotations .ApiStatus ;
18
23
import org .jetbrains .annotations .NotNull ;
19
24
@@ -30,21 +35,36 @@ public class ScreenshotUtils {
30
35
activity , AndroidMainThreadChecker .getInstance (), logger , buildInfoProvider );
31
36
}
32
37
38
+ @ SuppressLint ("NewApi" )
33
39
public static @ Nullable byte [] takeScreenshot (
34
40
final @ NotNull Activity activity ,
35
41
final @ NotNull IMainThreadChecker mainThreadChecker ,
36
42
final @ NotNull ILogger logger ,
37
43
final @ NotNull BuildInfoProvider buildInfoProvider ) {
38
44
39
- if (!isActivityValid (activity , buildInfoProvider )
40
- || activity .getWindow () == null
41
- || activity .getWindow ().getDecorView () == null
42
- || activity .getWindow ().getDecorView ().getRootView () == null ) {
45
+ if (!isActivityValid (activity , buildInfoProvider )) {
43
46
logger .log (SentryLevel .DEBUG , "Activity isn't valid, not taking screenshot." );
44
47
return null ;
45
48
}
46
49
47
- final View view = activity .getWindow ().getDecorView ().getRootView ();
50
+ final @ Nullable Window window = activity .getWindow ();
51
+ if (window == null ) {
52
+ logger .log (SentryLevel .DEBUG , "Activity window is null, not taking screenshot." );
53
+ return null ;
54
+ }
55
+
56
+ final @ Nullable View decorView = window .peekDecorView ();
57
+ if (decorView == null ) {
58
+ logger .log (SentryLevel .DEBUG , "DecorView is null, not taking screenshot." );
59
+ return null ;
60
+ }
61
+
62
+ final @ Nullable View view = decorView .getRootView ();
63
+ if (view == null ) {
64
+ logger .log (SentryLevel .DEBUG , "Root view is null, not taking screenshot." );
65
+ return null ;
66
+ }
67
+
48
68
if (view .getWidth () <= 0 || view .getHeight () <= 0 ) {
49
69
logger .log (SentryLevel .DEBUG , "View's width and height is zeroed, not taking screenshot." );
50
70
return null ;
@@ -55,20 +75,58 @@ public class ScreenshotUtils {
55
75
final Bitmap bitmap =
56
76
Bitmap .createBitmap (view .getWidth (), view .getHeight (), Bitmap .Config .ARGB_8888 );
57
77
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 );
78
+ final @ NotNull CountDownLatch latch = new CountDownLatch (1 );
79
+
80
+ // Use Pixel Copy API on new devices, fallback to canvas rendering on older ones
81
+ if (buildInfoProvider .getSdkInfoVersion () >= Build .VERSION_CODES .O ) {
82
+
83
+ final HandlerThread thread = new HandlerThread ("SentryScreenshot" );
84
+ thread .start ();
85
+
86
+ boolean success = false ;
87
+ try {
88
+ final Handler handler = new Handler (thread .getLooper ());
89
+ final AtomicBoolean copyResultSuccess = new AtomicBoolean (false );
90
+
91
+ PixelCopy .request (
92
+ window ,
93
+ bitmap ,
94
+ copyResult -> {
95
+ copyResultSuccess .set (copyResult == PixelCopy .SUCCESS );
67
96
latch .countDown ();
68
- } catch (Throwable e ) {
69
- logger .log (SentryLevel .ERROR , "Taking screenshot failed (view.draw)." , e );
70
- }
71
- });
97
+ },
98
+ handler );
99
+
100
+ success =
101
+ latch .await (CAPTURE_TIMEOUT_MS , TimeUnit .MILLISECONDS ) && copyResultSuccess .get ();
102
+ } catch (Throwable e ) {
103
+ // ignored
104
+ logger .log (SentryLevel .ERROR , "Taking screenshot using PixelCopy failed." , e );
105
+ } finally {
106
+ thread .quit ();
107
+ }
108
+
109
+ if (!success ) {
110
+ return null ;
111
+ }
112
+ } else {
113
+ final Canvas canvas = new Canvas (bitmap );
114
+ if (mainThreadChecker .isMainThread ()) {
115
+ view .draw (canvas );
116
+ latch .countDown ();
117
+ } else {
118
+ activity .runOnUiThread (
119
+ () -> {
120
+ try {
121
+ view .draw (canvas );
122
+ } catch (Throwable e ) {
123
+ logger .log (SentryLevel .ERROR , "Taking screenshot failed (view.draw)." , e );
124
+ } finally {
125
+ latch .countDown ();
126
+ }
127
+ });
128
+ }
129
+
72
130
if (!latch .await (CAPTURE_TIMEOUT_MS , TimeUnit .MILLISECONDS )) {
73
131
return null ;
74
132
}
0 commit comments