-
-
Notifications
You must be signed in to change notification settings - Fork 452
Use PixelCopy API for capturing screenshots on API level 24+ #3008
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
896cc16
746c5d0
1711e35
13df560
d1599d4
0eebd3b
44e04c8
da421ab
3145a36
499c540
413237c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,7 +5,11 @@ | |||||
import android.graphics.Bitmap; | ||||||
import android.graphics.Canvas; | ||||||
import android.os.Build; | ||||||
import android.os.Handler; | ||||||
import android.os.HandlerThread; | ||||||
import android.view.PixelCopy; | ||||||
import android.view.View; | ||||||
import android.view.Window; | ||||||
import androidx.annotation.Nullable; | ||||||
import io.sentry.ILogger; | ||||||
import io.sentry.SentryLevel; | ||||||
|
@@ -14,6 +18,7 @@ | |||||
import java.io.ByteArrayOutputStream; | ||||||
import java.util.concurrent.CountDownLatch; | ||||||
import java.util.concurrent.TimeUnit; | ||||||
import java.util.concurrent.atomic.AtomicBoolean; | ||||||
import org.jetbrains.annotations.ApiStatus; | ||||||
import org.jetbrains.annotations.NotNull; | ||||||
|
||||||
|
@@ -38,13 +43,14 @@ public class ScreenshotUtils { | |||||
|
||||||
if (!isActivityValid(activity, buildInfoProvider) | ||||||
|| activity.getWindow() == null | ||||||
|| activity.getWindow().getDecorView() == null | ||||||
|| activity.getWindow().getDecorView().getRootView() == null) { | ||||||
|| activity.getWindow().peekDecorView() == null | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|| activity.getWindow().peekDecorView().getRootView() == null) { | ||||||
logger.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot."); | ||||||
return null; | ||||||
} | ||||||
|
||||||
final View view = activity.getWindow().getDecorView().getRootView(); | ||||||
final Window window = activity.getWindow(); | ||||||
final View view = window.peekDecorView().getRootView(); | ||||||
if (view.getWidth() <= 0 || view.getHeight() <= 0) { | ||||||
logger.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot."); | ||||||
return null; | ||||||
|
@@ -55,20 +61,57 @@ public class ScreenshotUtils { | |||||
final Bitmap bitmap = | ||||||
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); | ||||||
|
||||||
final Canvas canvas = new Canvas(bitmap); | ||||||
if (mainThreadChecker.isMainThread()) { | ||||||
view.draw(canvas); | ||||||
} else { | ||||||
final @NotNull CountDownLatch latch = new CountDownLatch(1); | ||||||
activity.runOnUiThread( | ||||||
() -> { | ||||||
try { | ||||||
view.draw(canvas); | ||||||
final @NotNull CountDownLatch latch = new CountDownLatch(1); | ||||||
|
||||||
// Use Pixel Copy API on new devices, fallback to canvas rendering on older ones | ||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Completely forget about that, I've added some test using |
||||||
|
||||||
final HandlerThread thread = new HandlerThread("screenshot"); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
to make it more specific to our sdk |
||||||
thread.start(); | ||||||
|
||||||
boolean success = false; | ||||||
try { | ||||||
final Handler handler = new Handler(thread.getLooper()); | ||||||
final AtomicBoolean copyResultSuccess = new AtomicBoolean(false); | ||||||
|
||||||
PixelCopy.request( | ||||||
window, | ||||||
bitmap, | ||||||
copyResult -> { | ||||||
copyResultSuccess.set(copyResult == PixelCopy.SUCCESS); | ||||||
latch.countDown(); | ||||||
} catch (Throwable e) { | ||||||
logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e); | ||||||
} | ||||||
}); | ||||||
}, | ||||||
handler); | ||||||
|
||||||
success = | ||||||
latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS) && copyResultSuccess.get(); | ||||||
} catch (InterruptedException e) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I'd catch any throwable just to make sure |
||||||
// ignored | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} finally { | ||||||
thread.quit(); | ||||||
} | ||||||
|
||||||
if (!success) { | ||||||
return null; | ||||||
} | ||||||
} else { | ||||||
final Canvas canvas = new Canvas(bitmap); | ||||||
if (mainThreadChecker.isMainThread()) { | ||||||
view.draw(canvas); | ||||||
latch.countDown(); | ||||||
} else { | ||||||
activity.runOnUiThread( | ||||||
() -> { | ||||||
try { | ||||||
view.draw(canvas); | ||||||
} catch (Throwable e) { | ||||||
logger.log(SentryLevel.ERROR, "Taking screenshot failed (view.draw).", e); | ||||||
} finally { | ||||||
latch.countDown(); | ||||||
} | ||||||
}); | ||||||
} | ||||||
|
||||||
if (!latch.await(CAPTURE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { | ||||||
return null; | ||||||
} | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.