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