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
@@ -38,13 +43,14 @@ public class ScreenshotUtils {
38
43
39
44
if (!isActivityValid (activity , buildInfoProvider )
40
45
|| 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 ) {
43
48
logger .log (SentryLevel .DEBUG , "Activity isn't valid, not taking screenshot." );
44
49
return null ;
45
50
}
46
51
47
- final View view = activity .getWindow ().getDecorView ().getRootView ();
52
+ final Window window = activity .getWindow ();
53
+ final View view = window .peekDecorView ().getRootView ();
48
54
if (view .getWidth () <= 0 || view .getHeight () <= 0 ) {
49
55
logger .log (SentryLevel .DEBUG , "View's width and height is zeroed, not taking screenshot." );
50
56
return null ;
@@ -55,20 +61,57 @@ public class ScreenshotUtils {
55
61
final Bitmap bitmap =
56
62
Bitmap .createBitmap (view .getWidth (), view .getHeight (), Bitmap .Config .ARGB_8888 );
57
63
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 );
67
82
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
+
72
115
if (!latch .await (CAPTURE_TIMEOUT_MS , TimeUnit .MILLISECONDS )) {
73
116
return null ;
74
117
}
0 commit comments