Skip to content
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

Make possible screenshots on hybrid sdks (react-native) #2360

Merged
merged 33 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fc20d26
Fix do not add screenshots if should not apply scope data, make setCu…
krystofwoldrich Nov 14, 2022
d9a2e69
Format code
getsentry-bot Nov 14, 2022
6615b75
Expose ScreenshotEventProcessor to allow HybridSDKs to use it
krystofwoldrich Nov 14, 2022
0dddd51
Merge remote-tracking branch 'origin/feat-screenshots-for-hybrid-sdks…
krystofwoldrich Nov 15, 2022
d7b8109
Revert gradle.properties
krystofwoldrich Nov 15, 2022
33eb7e2
Format code
getsentry-bot Nov 15, 2022
98459b9
Return correctly when invalid activity
krystofwoldrich Nov 15, 2022
ba66365
Update api
krystofwoldrich Nov 15, 2022
465bbf3
Extract screenshot logic to static method, create activity holder sin…
krystofwoldrich Nov 16, 2022
97c3233
Merge branch 'main' into feat-screenshots-for-hybrid-sdks
krystofwoldrich Nov 16, 2022
706dc8e
Format code
getsentry-bot Nov 16, 2022
8c89da7
Refactor
krystofwoldrich Nov 16, 2022
2cce844
Make logger public
krystofwoldrich Nov 16, 2022
66b4e9e
Add configurable tag to android logger
krystofwoldrich Nov 16, 2022
fde7981
Format code
getsentry-bot Nov 16, 2022
ec05044
Update sentry-android-core/src/main/java/io/sentry/android/core/Andro…
krystofwoldrich Nov 16, 2022
2e19e51
Fix activity holder access, return build info
krystofwoldrich Nov 16, 2022
42e606b
Format code
getsentry-bot Nov 16, 2022
5aee06d
Merge branch 'main' into feat-screenshots-for-hybrid-sdks
krystofwoldrich Nov 16, 2022
0299512
Fix lint
krystofwoldrich Nov 16, 2022
6d57ab3
Merge branch 'main' into feat-screenshots-for-hybrid-sdks
krystofwoldrich Nov 16, 2022
03e03b9
Apply suggestions from code review
krystofwoldrich Nov 17, 2022
47e66be
Small refactor and add more hybrid sdks
krystofwoldrich Nov 17, 2022
ce5d2ce
Format code
getsentry-bot Nov 17, 2022
867a810
More refactoring annotations, finals
krystofwoldrich Nov 17, 2022
3e9008d
Add auto close steam
krystofwoldrich Nov 17, 2022
ce00db0
Merge branch 'main' into feat-screenshots-for-hybrid-sdks
krystofwoldrich Nov 21, 2022
0ce3f0e
HintUtils add only class annotation internal
krystofwoldrich Nov 21, 2022
baa0a64
CurrentActivityHolder make constructor private
krystofwoldrich Nov 21, 2022
8ebcaa2
Add changelog (don't attach screenshots to hybridSDKs)
krystofwoldrich Nov 21, 2022
8b49d10
Format code
getsentry-bot Nov 21, 2022
3bb055f
Update changelog
krystofwoldrich Nov 21, 2022
6a469aa
Revert "Update changelog"
krystofwoldrich Nov 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public final class io/sentry/android/core/PhoneStateBreadcrumbsIntegration : io/
public final class io/sentry/android/core/ScreenshotEventProcessor : android/app/Application$ActivityLifecycleCallbacks, io/sentry/EventProcessor, java/io/Closeable {
public fun <init> (Landroid/app/Application;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V
public fun close ()V
public static fun createInstance (Landroid/app/Application;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)Lio/sentry/android/core/ScreenshotEventProcessor;
public static fun getInstance ()Lio/sentry/android/core/ScreenshotEventProcessor;
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityDestroyed (Landroid/app/Activity;)V
public fun onActivityPaused (Landroid/app/Activity;)V
Expand All @@ -115,6 +117,8 @@ public final class io/sentry/android/core/ScreenshotEventProcessor : android/app
public fun onActivityStarted (Landroid/app/Activity;)V
public fun onActivityStopped (Landroid/app/Activity;)V
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
public fun setCurrentActivity (Landroid/app/Activity;)V
public fun takeScreenshot ()[B
}

public final class io/sentry/android/core/SentryAndroid {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ private static void installDefaultIntegrations(
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
}
options.addEventProcessor(
new ScreenshotEventProcessor((Application) context, options, buildInfoProvider));
ScreenshotEventProcessor.createInstance(
(Application) context, options, buildInfoProvider));
} else {
options
.getLogger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.sentry.Hint;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.util.HintUtils;
import io.sentry.util.Objects;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
Expand All @@ -33,6 +34,8 @@
public final class ScreenshotEventProcessor
implements EventProcessor, Application.ActivityLifecycleCallbacks, Closeable {

private static @NotNull ScreenshotEventProcessor instance;
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved

private final @NotNull Application application;
private final @NotNull SentryAndroidOptions options;
private @Nullable WeakReference<Activity> currentActivity;
Expand All @@ -51,10 +54,86 @@ public ScreenshotEventProcessor(
application.registerActivityLifecycleCallbacks(this);
}

public static ScreenshotEventProcessor createInstance(
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
final @NotNull Application application,
final @NotNull SentryAndroidOptions options,
final @NotNull BuildInfoProvider buildInfoProvider) {
ScreenshotEventProcessor.instance =
new ScreenshotEventProcessor(application, options, buildInfoProvider);
return ScreenshotEventProcessor.instance;
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
}

public static ScreenshotEventProcessor getInstance() {
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
return ScreenshotEventProcessor.instance;
}

public void setCurrentActivity(@NonNull Activity activity) {
if (currentActivity != null && currentActivity.get() == activity) {
return;
}
currentActivity = new WeakReference<>(activity);
}
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved

public byte[] takeScreenshot() {
if (currentActivity == null) {
return null;
}

final Activity activity = currentActivity.get();
if (!isActivityValid(activity)
|| activity.getWindow() == null
|| activity.getWindow().getDecorView() == null
|| activity.getWindow().getDecorView().getRootView() == null) {
this.options
.getLogger()
.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot.");
return null;
}

final View view = activity.getWindow().getDecorView().getRootView();
if (view.getWidth() <= 0 || view.getHeight() <= 0) {
this.options
.getLogger()
.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot.");
return null;
}

try {
// ARGB_8888 -> This configuration is very flexible and offers the best quality
final Bitmap bitmap =
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);

final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);

final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

// 0 meaning compress for small size, 100 meaning compress for max quality.
// Some formats, like PNG which is lossless, will ignore the quality setting.
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);

if (byteArrayOutputStream.size() <= 0) {
this.options
.getLogger()
.log(SentryLevel.DEBUG, "Screenshot is 0 bytes, not attaching the image.");
return null;
}

// screenshot png is around ~100-150 kb
return byteArrayOutputStream.toByteArray();
} catch (Throwable e) {
this.options.getLogger().log(SentryLevel.ERROR, "Taking screenshot failed.", e);
}
return null;
}

@SuppressWarnings("NullAway")
@Override
public @NotNull SentryEvent process(final @NotNull SentryEvent event, @NotNull Hint hint) {
if (!lifecycleCallbackInstalled) {
if (HintUtils.getIsFromHybridSdk(hint)
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
|| !lifecycleCallbackInstalled
|| !event.isErrored()
|| currentActivity == null) {
return event;
}
if (!options.isAttachScreenshot()) {
Expand All @@ -70,53 +149,13 @@ public ScreenshotEventProcessor(
return event;
}

if (event.isErrored() && currentActivity != null) {
final Activity activity = currentActivity.get();
if (isActivityValid(activity)
&& activity.getWindow() != null
&& activity.getWindow().getDecorView() != null
&& activity.getWindow().getDecorView().getRootView() != null) {
final View view = activity.getWindow().getDecorView().getRootView();

if (view.getWidth() > 0 && view.getHeight() > 0) {
try {
// ARGB_8888 -> This configuration is very flexible and offers the best quality
final Bitmap bitmap =
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);

final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);

final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

// 0 meaning compress for small size, 100 meaning compress for max quality.
// Some formats, like PNG which is lossless, will ignore the quality setting.
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);

if (byteArrayOutputStream.size() > 0) {
// screenshot png is around ~100-150 kb
hint.setScreenshot(Attachment.fromScreenshot(byteArrayOutputStream.toByteArray()));
hint.set(ANDROID_ACTIVITY, activity);
} else {
this.options
.getLogger()
.log(SentryLevel.DEBUG, "Screenshot is 0 bytes, not attaching the image.");
}
} catch (Throwable e) {
this.options.getLogger().log(SentryLevel.ERROR, "Taking screenshot failed.", e);
}
} else {
this.options
.getLogger()
.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot.");
}
} else {
this.options
.getLogger()
.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot.");
}
final byte[] screenshot = takeScreenshot();
if (screenshot == null) {
return event;
}

hint.setScreenshot(Attachment.fromScreenshot(screenshot));
hint.set(ANDROID_ACTIVITY, currentActivity.get());
return event;
}

Expand Down Expand Up @@ -167,13 +206,6 @@ private void cleanCurrentActivity(@NonNull Activity activity) {
}
}

private void setCurrentActivity(@NonNull Activity activity) {
if (currentActivity != null && currentActivity.get() == activity) {
return;
}
currentActivity = new WeakReference<>(activity);
}

@SuppressLint("NewApi")
private boolean isActivityValid(@Nullable Activity activity) {
if (activity == null) {
Expand Down
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -1897,6 +1897,9 @@ public final class io/sentry/TypeCheckHint {
public static final field OKHTTP_RESPONSE Ljava/lang/String;
public static final field OPEN_FEIGN_REQUEST Ljava/lang/String;
public static final field OPEN_FEIGN_RESPONSE Ljava/lang/String;
public static final field SENTRY_DART_SDK_NAME Ljava/lang/String;
public static final field SENTRY_IS_FROM_HYBRID_SDK Ljava/lang/String;
public static final field SENTRY_REACT_NATIVE_SDK_NAME Ljava/lang/String;
public static final field SENTRY_SYNTHETIC_EXCEPTION Ljava/lang/String;
public static final field SENTRY_TYPE_CHECK_HINT Ljava/lang/String;
public static final field SERVLET_REQUEST Ljava/lang/String;
Expand Down Expand Up @@ -3301,12 +3304,15 @@ public final class io/sentry/util/FileUtils {

public final class io/sentry/util/HintUtils {
public static fun createWithTypeCheckHint (Ljava/lang/Object;)Lio/sentry/Hint;
public static fun getIsFromHybridSdk (Lio/sentry/Hint;)Z
public static fun getSentrySdkHint (Lio/sentry/Hint;)Ljava/lang/Object;
public static fun hasType (Lio/sentry/Hint;Ljava/lang/Class;)Z
public static fun runIfDoesNotHaveType (Lio/sentry/Hint;Ljava/lang/Class;Lio/sentry/util/HintUtils$SentryNullableConsumer;)V
public static fun runIfHasType (Lio/sentry/Hint;Ljava/lang/Class;Lio/sentry/util/HintUtils$SentryConsumer;)V
public static fun runIfHasType (Lio/sentry/Hint;Ljava/lang/Class;Lio/sentry/util/HintUtils$SentryConsumer;Lio/sentry/util/HintUtils$SentryHintFallback;)V
public static fun runIfHasTypeLogIfNot (Lio/sentry/Hint;Ljava/lang/Class;Lio/sentry/ILogger;Lio/sentry/util/HintUtils$SentryConsumer;)V
public static fun setIsFromHybridSdk (Lio/sentry/Hint;Ljava/lang/String;)V
public static fun setIsFromHybridSdk (Lio/sentry/Hint;Z)V
public static fun setTypeCheckHint (Lio/sentry/Hint;Ljava/lang/Object;)V
public static fun shouldApplyScopeData (Lio/sentry/Hint;)Z
}
Expand Down
3 changes: 3 additions & 0 deletions sentry/src/main/java/io/sentry/OutboxSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN
if (event == null) {
logEnvelopeItemNull(item, currentItem);
} else {
if (event.getSdk() != null) {
HintUtils.setIsFromHybridSdk(hint, event.getSdk().getName());
}
if (envelope.getHeader().getEventId() != null
&& !envelope.getHeader().getEventId().equals(event.getEventId())) {
logUnexpectedEventId(envelope, event.getEventId(), currentItem);
Expand Down
8 changes: 8 additions & 0 deletions sentry/src/main/java/io/sentry/TypeCheckHint.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ public final class TypeCheckHint {

@ApiStatus.Internal public static final String SENTRY_TYPE_CHECK_HINT = "sentry:typeCheckHint";

@ApiStatus.Internal
public static final String SENTRY_IS_FROM_HYBRID_SDK = "sentry:isFromHybridSdk";

@ApiStatus.Internal
public static final String SENTRY_REACT_NATIVE_SDK_NAME = "sentry.javascript.react-native";

@ApiStatus.Internal public static final String SENTRY_DART_SDK_NAME = "sentry.dart";

/** Used for Synthetic exceptions. */
public static final String SENTRY_SYNTHETIC_EXCEPTION = "syntheticException";

Expand Down
21 changes: 21 additions & 0 deletions sentry/src/main/java/io/sentry/util/HintUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.sentry.util;

import static io.sentry.TypeCheckHint.SENTRY_DART_SDK_NAME;
import static io.sentry.TypeCheckHint.SENTRY_IS_FROM_HYBRID_SDK;
import static io.sentry.TypeCheckHint.SENTRY_REACT_NATIVE_SDK_NAME;
import static io.sentry.TypeCheckHint.SENTRY_TYPE_CHECK_HINT;

import io.sentry.Hint;
Expand All @@ -16,6 +19,24 @@ public final class HintUtils {

private HintUtils() {}

@ApiStatus.Internal
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
public static void setIsFromHybridSdk(Hint hint, String sdkName) {
if (sdkName.startsWith(SENTRY_REACT_NATIVE_SDK_NAME)
|| sdkName.startsWith(SENTRY_DART_SDK_NAME)) {
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
HintUtils.setIsFromHybridSdk(hint, true);
}
}

@ApiStatus.Internal
public static void setIsFromHybridSdk(Hint hint, boolean isFromHybridSdk) {
hint.set(SENTRY_IS_FROM_HYBRID_SDK, isFromHybridSdk);
}

@ApiStatus.Internal
public static boolean getIsFromHybridSdk(Hint hint) {
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
return Boolean.TRUE.equals(hint.getAs(SENTRY_IS_FROM_HYBRID_SDK, Boolean.class));
}

@ApiStatus.Internal
public static Hint createWithTypeCheckHint(Object typeCheckHint) {
Hint hint = new Hint();
Expand Down