Skip to content

Commit e28984f

Browse files
Merge 66b4e9e into 507f924
2 parents 507f924 + 66b4e9e commit e28984f

File tree

12 files changed

+213
-87
lines changed

12 files changed

+213
-87
lines changed

sentry-android-core/api/sentry-android-core.api

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
2222
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
2323
}
2424

25+
public final class io/sentry/android/core/AndroidLogger : io/sentry/ILogger {
26+
public fun <init> ()V
27+
public fun <init> (Ljava/lang/String;)V
28+
public fun isEnabled (Lio/sentry/SentryLevel;)Z
29+
public fun log (Lio/sentry/SentryLevel;Ljava/lang/String;Ljava/lang/Throwable;)V
30+
public fun log (Lio/sentry/SentryLevel;Ljava/lang/String;[Ljava/lang/Object;)V
31+
public fun log (Lio/sentry/SentryLevel;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
32+
}
33+
2534
public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable {
2635
public fun <init> (Landroid/content/Context;)V
2736
public fun close ()V
@@ -72,6 +81,14 @@ public final class io/sentry/android/core/BuildInfoProvider {
7281
public fun isEmulator ()Ljava/lang/Boolean;
7382
}
7483

84+
public class io/sentry/android/core/CurrentActivityHolder {
85+
public fun <init> ()V
86+
public fun clearActivity ()V
87+
public fun getActivity ()Landroid/app/Activity;
88+
public static fun getInstance ()Lio/sentry/android/core/CurrentActivityHolder;
89+
public fun setActivity (Landroid/app/Activity;)V
90+
}
91+
7592
public abstract class io/sentry/android/core/EnvelopeFileObserverIntegration : io/sentry/Integration, java/io/Closeable {
7693
public fun <init> ()V
7794
public fun close ()V
@@ -105,7 +122,7 @@ public final class io/sentry/android/core/PhoneStateBreadcrumbsIntegration : io/
105122
}
106123

107124
public final class io/sentry/android/core/ScreenshotEventProcessor : android/app/Application$ActivityLifecycleCallbacks, io/sentry/EventProcessor, java/io/Closeable {
108-
public fun <init> (Landroid/app/Application;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V
125+
public fun <init> (Landroid/app/Application;Lio/sentry/android/core/SentryAndroidOptions;)V
109126
public fun close ()V
110127
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
111128
public fun onActivityDestroyed (Landroid/app/Activity;)V

sentry-android-core/src/main/java/io/sentry/android/core/AndroidLogger.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33
import android.util.Log;
44
import io.sentry.ILogger;
55
import io.sentry.SentryLevel;
6+
import org.jetbrains.annotations.ApiStatus;
67
import org.jetbrains.annotations.NotNull;
78
import org.jetbrains.annotations.Nullable;
89

9-
final class AndroidLogger implements ILogger {
10+
@ApiStatus.Internal
11+
public final class AndroidLogger implements ILogger {
1012

11-
private static final String tag = "Sentry";
13+
private final String tag;
14+
15+
public AndroidLogger(){
16+
tag = "Sentry";
17+
}
18+
19+
public AndroidLogger(@NotNull String tag) {
20+
this.tag = tag;
21+
}
1222

1323
@SuppressWarnings("AnnotateFormatMethod")
1424
@Override

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,7 @@ private static void installDefaultIntegrations(
209209
if (isFragmentAvailable) {
210210
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
211211
}
212-
options.addEventProcessor(
213-
new ScreenshotEventProcessor((Application) context, options, buildInfoProvider));
212+
options.addEventProcessor(new ScreenshotEventProcessor((Application) context, options));
214213
} else {
215214
options
216215
.getLogger()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.sentry.android.core;
2+
3+
import android.app.Activity;
4+
import androidx.annotation.NonNull;
5+
import androidx.annotation.Nullable;
6+
import java.lang.ref.WeakReference;
7+
import org.jetbrains.annotations.ApiStatus;
8+
9+
@ApiStatus.Internal
10+
public class CurrentActivityHolder {
11+
12+
private static @Nullable CurrentActivityHolder instance;
13+
14+
private @Nullable WeakReference<Activity> currentActivity;
15+
16+
public static CurrentActivityHolder getInstance() {
17+
if (instance != null) {
18+
return instance;
19+
}
20+
instance = new CurrentActivityHolder();
21+
return instance;
22+
}
23+
24+
public @Nullable Activity getActivity() {
25+
if (currentActivity != null) {
26+
return currentActivity.get();
27+
}
28+
return null;
29+
}
30+
31+
public void setActivity(@NonNull Activity activity) {
32+
if (currentActivity != null && currentActivity.get() == activity) {
33+
return;
34+
}
35+
36+
currentActivity = new WeakReference<>(activity);
37+
}
38+
39+
public void clearActivity() {
40+
currentActivity = null;
41+
}
42+
}
Lines changed: 20 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
11
package io.sentry.android.core;
22

33
import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY;
4+
import static io.sentry.android.core.internal.util.ScreenshotUtils.takeScreenshot;
45

5-
import android.annotation.SuppressLint;
66
import android.app.Activity;
77
import android.app.Application;
8-
import android.graphics.Bitmap;
9-
import android.graphics.Canvas;
10-
import android.os.Build;
118
import android.os.Bundle;
12-
import android.view.View;
139
import androidx.annotation.NonNull;
1410
import androidx.annotation.Nullable;
1511
import io.sentry.Attachment;
1612
import io.sentry.EventProcessor;
1713
import io.sentry.Hint;
1814
import io.sentry.SentryEvent;
1915
import io.sentry.SentryLevel;
16+
import io.sentry.util.HintUtils;
2017
import io.sentry.util.Objects;
21-
import java.io.ByteArrayOutputStream;
2218
import java.io.Closeable;
2319
import java.io.IOException;
24-
import java.lang.ref.WeakReference;
2520
import org.jetbrains.annotations.ApiStatus;
2621
import org.jetbrains.annotations.NotNull;
2722

@@ -35,26 +30,22 @@ public final class ScreenshotEventProcessor
3530

3631
private final @NotNull Application application;
3732
private final @NotNull SentryAndroidOptions options;
38-
private @Nullable WeakReference<Activity> currentActivity;
39-
private final @NotNull BuildInfoProvider buildInfoProvider;
33+
private final @NotNull CurrentActivityHolder currentActivityHolder;
4034
private boolean lifecycleCallbackInstalled = true;
4135

4236
public ScreenshotEventProcessor(
43-
final @NotNull Application application,
44-
final @NotNull SentryAndroidOptions options,
45-
final @NotNull BuildInfoProvider buildInfoProvider) {
37+
final @NotNull Application application, final @NotNull SentryAndroidOptions options) {
4638
this.application = Objects.requireNonNull(application, "Application is required");
4739
this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required");
48-
this.buildInfoProvider =
49-
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
40+
this.currentActivityHolder = CurrentActivityHolder.getInstance();
5041

5142
application.registerActivityLifecycleCallbacks(this);
5243
}
5344

5445
@SuppressWarnings("NullAway")
5546
@Override
5647
public @NotNull SentryEvent process(final @NotNull SentryEvent event, @NotNull Hint hint) {
57-
if (!lifecycleCallbackInstalled) {
48+
if (!lifecycleCallbackInstalled || !event.isErrored()) {
5849
return event;
5950
}
6051
if (!options.isAttachScreenshot()) {
@@ -69,60 +60,24 @@ public ScreenshotEventProcessor(
6960

7061
return event;
7162
}
63+
if (currentActivityHolder.getActivity() == null || HintUtils.isFromHybridSdk(hint)) {
64+
return event;
65+
}
7266

73-
if (event.isErrored() && currentActivity != null) {
74-
final Activity activity = currentActivity.get();
75-
if (isActivityValid(activity)
76-
&& activity.getWindow() != null
77-
&& activity.getWindow().getDecorView() != null
78-
&& activity.getWindow().getDecorView().getRootView() != null) {
79-
final View view = activity.getWindow().getDecorView().getRootView();
80-
81-
if (view.getWidth() > 0 && view.getHeight() > 0) {
82-
try {
83-
// ARGB_8888 -> This configuration is very flexible and offers the best quality
84-
final Bitmap bitmap =
85-
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
86-
87-
final Canvas canvas = new Canvas(bitmap);
88-
view.draw(canvas);
89-
90-
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
91-
92-
// 0 meaning compress for small size, 100 meaning compress for max quality.
93-
// Some formats, like PNG which is lossless, will ignore the quality setting.
94-
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
95-
96-
if (byteArrayOutputStream.size() > 0) {
97-
// screenshot png is around ~100-150 kb
98-
hint.setScreenshot(Attachment.fromScreenshot(byteArrayOutputStream.toByteArray()));
99-
hint.set(ANDROID_ACTIVITY, activity);
100-
} else {
101-
this.options
102-
.getLogger()
103-
.log(SentryLevel.DEBUG, "Screenshot is 0 bytes, not attaching the image.");
104-
}
105-
} catch (Throwable e) {
106-
this.options.getLogger().log(SentryLevel.ERROR, "Taking screenshot failed.", e);
107-
}
108-
} else {
109-
this.options
110-
.getLogger()
111-
.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot.");
112-
}
113-
} else {
114-
this.options
115-
.getLogger()
116-
.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot.");
117-
}
67+
final byte[] screenshot =
68+
takeScreenshot(currentActivityHolder.getActivity(), options.getLogger());
69+
if (screenshot == null) {
70+
return event;
11871
}
11972

73+
hint.setScreenshot(Attachment.fromScreenshot(screenshot));
74+
hint.set(ANDROID_ACTIVITY, currentActivityHolder.getActivity());
12075
return event;
12176
}
12277

12378
@Override
12479
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
125-
setCurrentActivity(activity);
80+
currentActivityHolder.setActivity(activity);
12681
}
12782

12883
@Override
@@ -157,32 +112,17 @@ public void onActivityDestroyed(@NonNull Activity activity) {
157112
public void close() throws IOException {
158113
if (options.isAttachScreenshot()) {
159114
application.unregisterActivityLifecycleCallbacks(this);
160-
currentActivity = null;
115+
currentActivityHolder.clearActivity();
161116
}
162117
}
163118

164119
private void cleanCurrentActivity(@NonNull Activity activity) {
165-
if (currentActivity != null && currentActivity.get() == activity) {
166-
currentActivity = null;
120+
if (currentActivityHolder.getActivity() == activity) {
121+
currentActivityHolder.clearActivity();
167122
}
168123
}
169124

170125
private void setCurrentActivity(@NonNull Activity activity) {
171-
if (currentActivity != null && currentActivity.get() == activity) {
172-
return;
173-
}
174-
currentActivity = new WeakReference<>(activity);
175-
}
176-
177-
@SuppressLint("NewApi")
178-
private boolean isActivityValid(@Nullable Activity activity) {
179-
if (activity == null) {
180-
return false;
181-
}
182-
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
183-
return !activity.isFinishing() && !activity.isDestroyed();
184-
} else {
185-
return !activity.isFinishing();
186-
}
126+
currentActivityHolder.setActivity(activity);
187127
}
188128
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.sentry.android.core.internal.util;
2+
3+
import android.app.Activity;
4+
import android.os.Build;
5+
import androidx.annotation.Nullable;
6+
import org.jetbrains.annotations.ApiStatus;
7+
8+
@ApiStatus.Internal
9+
public class ActivityUtils {
10+
public static boolean isActivityValid(@Nullable Activity activity) {
11+
if (activity == null) {
12+
return false;
13+
}
14+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
15+
return !activity.isFinishing() && !activity.isDestroyed();
16+
} else {
17+
return !activity.isFinishing();
18+
}
19+
}
20+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.sentry.android.core.internal.util;
2+
3+
import static io.sentry.android.core.internal.util.ActivityUtils.isActivityValid;
4+
5+
import android.app.Activity;
6+
import android.graphics.Bitmap;
7+
import android.graphics.Canvas;
8+
import android.view.View;
9+
import androidx.annotation.Nullable;
10+
import io.sentry.ILogger;
11+
import io.sentry.SentryLevel;
12+
import java.io.ByteArrayOutputStream;
13+
import org.jetbrains.annotations.ApiStatus;
14+
import org.jetbrains.annotations.NotNull;
15+
16+
@ApiStatus.Internal
17+
public class ScreenshotUtils {
18+
public static @Nullable byte[] takeScreenshot(
19+
final @Nullable Activity activity, final @NotNull ILogger logger) {
20+
if (activity == null) {
21+
return null;
22+
}
23+
24+
if (!isActivityValid(activity)
25+
|| activity.getWindow() == null
26+
|| activity.getWindow().getDecorView() == null
27+
|| activity.getWindow().getDecorView().getRootView() == null) {
28+
logger.log(SentryLevel.DEBUG, "Activity isn't valid, not taking screenshot.");
29+
return null;
30+
}
31+
32+
final View view = activity.getWindow().getDecorView().getRootView();
33+
if (view.getWidth() <= 0 || view.getHeight() <= 0) {
34+
logger.log(SentryLevel.DEBUG, "View's width and height is zeroed, not taking screenshot.");
35+
return null;
36+
}
37+
38+
try {
39+
// ARGB_8888 -> This configuration is very flexible and offers the best quality
40+
final Bitmap bitmap =
41+
Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
42+
43+
final Canvas canvas = new Canvas(bitmap);
44+
view.draw(canvas);
45+
46+
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
47+
48+
// 0 meaning compress for small size, 100 meaning compress for max quality.
49+
// Some formats, like PNG which is lossless, will ignore the quality setting.
50+
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
51+
52+
if (byteArrayOutputStream.size() <= 0) {
53+
logger.log(SentryLevel.DEBUG, "Screenshot is 0 bytes, not attaching the image.");
54+
return null;
55+
}
56+
57+
// screenshot png is around ~100-150 kb
58+
return byteArrayOutputStream.toByteArray();
59+
} catch (Throwable e) {
60+
logger.log(SentryLevel.ERROR, "Taking screenshot failed.", e);
61+
}
62+
return null;
63+
}
64+
}

sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class ScreenshotEventProcessorTest {
2828

2929
private class Fixture {
3030
val application = mock<Application>()
31-
val buildInfo = mock<BuildInfoProvider>()
3231
val activity = mock<Activity>()
3332
val window = mock<Window>()
3433
val view = mock<View>()
@@ -49,7 +48,7 @@ class ScreenshotEventProcessorTest {
4948
fun getSut(attachScreenshot: Boolean = false): ScreenshotEventProcessor {
5049
options.isAttachScreenshot = attachScreenshot
5150

52-
return ScreenshotEventProcessor(application, options, buildInfo)
51+
return ScreenshotEventProcessor(application, options)
5352
}
5453
}
5554

0 commit comments

Comments
 (0)