Skip to content

Commit

Permalink
Merge f5602f0 into 754021c
Browse files Browse the repository at this point in the history
  • Loading branch information
marandaneto authored Feb 7, 2023
2 parents 754021c + f5602f0 commit 4edebf2
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add `main` flag to threads and `in_foreground` flag for app contexts ([#2516](https://github.com/getsentry/sentry-java/pull/2516))

### Fixes

- Ignore Shutdown in progress when closing ShutdownHookIntegration ([#2521](https://github.com/getsentry/sentry-java/pull/2521))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public DefaultAndroidEventProcessor(
this.options = Objects.requireNonNull(options, "The options object is required.");

ExecutorService executorService = Executors.newSingleThreadExecutor();
// dont ref. to method reference, theres a bug on it
// don't ref. to method reference, theres a bug on it
//noinspection Convert2MethodRef
contextData = executorService.submit(() -> loadContextData());

Expand Down Expand Up @@ -128,8 +128,8 @@ public DefaultAndroidEventProcessor(
// we only set memory data if it's not a hard crash, when it's a hard crash the event is
// enriched on restart, so non static data might be wrong, eg lowMemory or availMem will
// be different if the App. crashes because of OOM.
processNonCachedEvent(event);
setThreads(event);
processNonCachedEvent(event, hint);
setThreads(event, hint);
}

setCommons(event, true, applyScopeData);
Expand Down Expand Up @@ -201,23 +201,34 @@ private void mergeOS(final @NotNull SentryBaseEvent event) {
}

// Data to be applied to events that was created in the running process
private void processNonCachedEvent(final @NotNull SentryBaseEvent event) {
private void processNonCachedEvent(
final @NotNull SentryBaseEvent event, final @NotNull Hint hint) {
App app = event.getContexts().getApp();
if (app == null) {
app = new App();
}
setAppExtras(app);
setAppExtras(app, hint);

setPackageInfo(event, app);

event.getContexts().setApp(app);
}

private void setThreads(final @NotNull SentryEvent event) {
private void setThreads(final @NotNull SentryEvent event, final @NotNull Hint hint) {
if (event.getThreads() != null) {
for (SentryThread thread : event.getThreads()) {
final boolean isHybridSDK = HintUtils.isFromHybridSdk(hint);

for (final SentryThread thread : event.getThreads()) {
final boolean isMainThread = AndroidMainThreadChecker.getInstance().isMainThread(thread);

// TODO: Fix https://github.com/getsentry/team-mobile/issues/47
if (thread.isCurrent() == null) {
thread.setCurrent(AndroidMainThreadChecker.getInstance().isMainThread(thread));
thread.setCurrent(isMainThread);
}

// This should not be set by Hybrid SDKs since they have their own threading model
if (!isHybridSDK && thread.isMain() == null) {
thread.setMain(isMainThread);
}
}
}
Expand All @@ -241,9 +252,19 @@ private void setDist(final @NotNull SentryBaseEvent event, final @NotNull String
}
}

private void setAppExtras(final @NotNull App app) {
private void setAppExtras(final @NotNull App app, final @NotNull Hint hint) {
app.setAppName(getApplicationName());
app.setAppStartTime(DateUtils.toUtilDate(AppStartState.getInstance().getAppStartTime()));

// This should not be set by Hybrid SDKs since they have their own app's lifecycle
if (!HintUtils.isFromHybridSdk(hint) && app.getInForeground() == null) {
// This feature depends on the AppLifecycleIntegration being installed, so only if
// enableAutoSessionTracking or enableAppLifecycleBreadcrumbs are enabled.
final @Nullable Boolean isBackground = AppState.getInstance().isInBackground();
if (isBackground != null) {
app.setInForeground(!isBackground);
}
}
}

@SuppressWarnings("deprecation")
Expand All @@ -256,21 +277,21 @@ private void setAppExtras(final @NotNull App app) {
return Build.CPU_ABI2;
}

@SuppressWarnings({"ObsoleteSdkInt", "deprecation"})
@SuppressWarnings({"ObsoleteSdkInt", "deprecation", "NewApi"})
private void setArchitectures(final @NotNull Device device) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String[] supportedAbis = Build.SUPPORTED_ABIS;
device.setArchs(supportedAbis);
final String[] supportedAbis;
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.LOLLIPOP) {
supportedAbis = Build.SUPPORTED_ABIS;
} else {
String[] supportedAbis = {getAbi(), getAbi2()};
device.setArchs(supportedAbis);
supportedAbis = new String[] {getAbi(), getAbi2()};
// we were not checking CPU_ABI2, but I've added to the list now
}
device.setArchs(supportedAbis);
}

@SuppressWarnings("ObsoleteSdkInt")
@SuppressWarnings({"ObsoleteSdkInt", "NewApi"})
private @NotNull Long getMemorySize(final @NotNull ActivityManager.MemoryInfo memInfo) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN) {
return memInfo.totalMem;
}
// using Runtime as a fallback
Expand Down Expand Up @@ -393,17 +414,18 @@ private void setDeviceIO(final @NotNull Device device, final boolean applyScopeD
}
}

@SuppressWarnings("ObsoleteSdkInt")
@SuppressWarnings({"ObsoleteSdkInt", "NewApi"})
private @Nullable String getDeviceName() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return Settings.Global.getString(context.getContentResolver(), "device_name");
} else {
return null;
}
}

@SuppressWarnings("NewApi")
private TimeZone getTimeZone() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.N) {
LocaleList locales = context.getResources().getConfiguration().getLocales();
if (!locales.isEmpty()) {
Locale locale = locales.get(0);
Expand Down Expand Up @@ -557,9 +579,9 @@ private TimeZone getTimeZone() {
}
}

@SuppressWarnings("ObsoleteSdkInt")
@SuppressWarnings({"ObsoleteSdkInt", "NewApi"})
private long getBlockSizeLong(final @NotNull StatFs stat) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return stat.getBlockSizeLong();
}
return getBlockSizeDep(stat);
Expand All @@ -570,9 +592,9 @@ private int getBlockSizeDep(final @NotNull StatFs stat) {
return stat.getBlockSize();
}

@SuppressWarnings("ObsoleteSdkInt")
@SuppressWarnings({"ObsoleteSdkInt", "NewApi"})
private long getBlockCountLong(final @NotNull StatFs stat) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return stat.getBlockCountLong();
}
return getBlockCountDep(stat);
Expand All @@ -583,9 +605,9 @@ private int getBlockCountDep(final @NotNull StatFs stat) {
return stat.getBlockCount();
}

@SuppressWarnings("ObsoleteSdkInt")
@SuppressWarnings({"ObsoleteSdkInt", "NewApi"})
private long getAvailableBlocksLong(final @NotNull StatFs stat) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return stat.getAvailableBlocksLong();
}
return getAvailableBlocksDep(stat);
Expand Down Expand Up @@ -627,9 +649,9 @@ private int getAvailableBlocksDep(final @NotNull StatFs stat) {
return null;
}

@SuppressWarnings("ObsoleteSdkInt")
@SuppressWarnings({"ObsoleteSdkInt", "NewApi"})
private @Nullable File[] getExternalFilesDirs() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.KITKAT) {
return context.getExternalFilesDirs(null);
} else {
File single = context.getExternalFilesDir(null);
Expand Down Expand Up @@ -907,7 +929,7 @@ private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) {
final boolean applyScopeData = shouldApplyScopeData(transaction, hint);

if (applyScopeData) {
processNonCachedEvent(transaction);
processNonCachedEvent(transaction, hint);
}

setCommons(transaction, false, applyScopeData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ final class LifecycleWatcher implements DefaultLifecycleObserver {
public void onStart(final @NotNull LifecycleOwner owner) {
startSession();
addAppBreadcrumb("foreground");

// Consider using owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED);
// in the future.
AppState.getInstance().setInBackground(false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class DefaultAndroidEventProcessorTest {
private class Fixture {
val buildInfo = mock<BuildInfoProvider>()
val options = SentryAndroidOptions().apply {
setDebug(true)
isDebug = true
setLogger(mock())
sdkVersion = SdkVersion("test", "1.2.3")
}
Expand All @@ -77,6 +77,7 @@ class DefaultAndroidEventProcessorTest {
@BeforeTest
fun `set up`() {
context = ApplicationProvider.getApplicationContext()
AppState.getInstance().resetInstance()
}

@Test
Expand Down Expand Up @@ -161,7 +162,7 @@ class DefaultAndroidEventProcessorTest {
}

@Test
fun `Current should be true if it comes from main thread`() {
fun `Current and Main should be true if it comes from main thread`() {
val sut = fixture.getSut(context)

val sentryThread = SentryThread().apply {
Expand All @@ -174,6 +175,7 @@ class DefaultAndroidEventProcessorTest {
assertNotNull(sut.process(event, Hint())) {
assertNotNull(it.threads) { threads ->
assertTrue(threads.first().isCurrent == true)
assertTrue(threads.first().isMain == true)
}
}
}
Expand All @@ -193,6 +195,7 @@ class DefaultAndroidEventProcessorTest {
assertNotNull(sut.process(event, Hint())) {
assertNotNull(it.threads) { threads ->
assertFalse(threads.first().isCurrent == true)
assertFalse(threads.first().isMain == true)
}
}
}
Expand Down Expand Up @@ -497,4 +500,28 @@ class DefaultAndroidEventProcessorTest {
assertEquals("en_US", device.locale)
}
}

@Test
fun `Event sets InForeground to true if not in the background`() {
val sut = fixture.getSut(context)

AppState.getInstance().setInBackground(false)

assertNotNull(sut.process(SentryEvent(), Hint())) {
val app = it.contexts.app!!
assertTrue(app.inForeground!!)
}
}

@Test
fun `Event sets InForeground to false if in the background`() {
val sut = fixture.getSut(context)

AppState.getInstance().setInBackground(true)

assertNotNull(sut.process(SentryEvent(), Hint())) {
val app = it.contexts.app!!
assertFalse(app.inForeground!!)
}
}
}
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -2510,6 +2510,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
public fun getAppVersion ()Ljava/lang/String;
public fun getBuildType ()Ljava/lang/String;
public fun getDeviceAppHash ()Ljava/lang/String;
public fun getInForeground ()Ljava/lang/Boolean;
public fun getPermissions ()Ljava/util/Map;
public fun getUnknown ()Ljava/util/Map;
public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V
Expand All @@ -2520,6 +2521,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
public fun setAppVersion (Ljava/lang/String;)V
public fun setBuildType (Ljava/lang/String;)V
public fun setDeviceAppHash (Ljava/lang/String;)V
public fun setInForeground (Ljava/lang/Boolean;)V
public fun setPermissions (Ljava/util/Map;)V
public fun setUnknown (Ljava/util/Map;)V
}
Expand All @@ -2539,6 +2541,7 @@ public final class io/sentry/protocol/App$JsonKeys {
public static final field APP_VERSION Ljava/lang/String;
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEVICE_APP_HASH Ljava/lang/String;
public static final field IN_FOREGROUND Ljava/lang/String;
public fun <init> ()V
}

Expand Down Expand Up @@ -3344,11 +3347,13 @@ public final class io/sentry/protocol/SentryThread : io/sentry/JsonSerializable,
public fun isCrashed ()Ljava/lang/Boolean;
public fun isCurrent ()Ljava/lang/Boolean;
public fun isDaemon ()Ljava/lang/Boolean;
public fun isMain ()Ljava/lang/Boolean;
public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V
public fun setCrashed (Ljava/lang/Boolean;)V
public fun setCurrent (Ljava/lang/Boolean;)V
public fun setDaemon (Ljava/lang/Boolean;)V
public fun setId (Ljava/lang/Long;)V
public fun setMain (Ljava/lang/Boolean;)V
public fun setName (Ljava/lang/String;)V
public fun setPriority (Ljava/lang/Integer;)V
public fun setStacktrace (Lio/sentry/protocol/SentryStackTrace;)V
Expand All @@ -3367,6 +3372,7 @@ public final class io/sentry/protocol/SentryThread$JsonKeys {
public static final field CURRENT Ljava/lang/String;
public static final field DAEMON Ljava/lang/String;
public static final field ID Ljava/lang/String;
public static final field MAIN Ljava/lang/String;
public static final field NAME Ljava/lang/String;
public static final field PRIORITY Ljava/lang/String;
public static final field STACKTRACE Ljava/lang/String;
Expand Down
22 changes: 22 additions & 0 deletions sentry/src/main/java/io/sentry/protocol/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public final class App implements JsonUnknown, JsonSerializable {
private @Nullable String appBuild;
/** Application permissions in the form of "permission_name" : "granted|not_granted" */
private @Nullable Map<String, String> permissions;
/**
* A flag indicating whether the app is in foreground or not. An app is in foreground when it's
* visible to the user.
*/
private @Nullable Boolean inForeground;

public App() {}

Expand All @@ -50,6 +55,7 @@ public App() {}
this.buildType = app.buildType;
this.deviceAppHash = app.deviceAppHash;
this.permissions = CollectionUtils.newConcurrentHashMap(app.permissions);
this.inForeground = app.inForeground;
this.unknown = CollectionUtils.newConcurrentHashMap(app.unknown);
}

Expand Down Expand Up @@ -122,6 +128,15 @@ public void setPermissions(@Nullable Map<String, String> permissions) {
this.permissions = permissions;
}

@Nullable
public Boolean getInForeground() {
return inForeground;
}

public void setInForeground(final @Nullable Boolean inForeground) {
this.inForeground = inForeground;
}

// region json

@Nullable
Expand All @@ -144,6 +159,7 @@ public static final class JsonKeys {
public static final String APP_VERSION = "app_version";
public static final String APP_BUILD = "app_build";
public static final String APP_PERMISSIONS = "permissions";
public static final String IN_FOREGROUND = "in_foreground";
}

@Override
Expand Down Expand Up @@ -174,6 +190,9 @@ public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger)
if (permissions != null && !permissions.isEmpty()) {
writer.name(JsonKeys.APP_PERMISSIONS).value(logger, permissions);
}
if (inForeground != null) {
writer.name(JsonKeys.IN_FOREGROUND).value(inForeground);
}
if (unknown != null) {
for (String key : unknown.keySet()) {
Object value = unknown.get(key);
Expand Down Expand Up @@ -220,6 +239,9 @@ public static final class Deserializer implements JsonDeserializer<App> {
CollectionUtils.newConcurrentHashMap(
(Map<String, String>) reader.nextObjectOrNull());
break;
case JsonKeys.IN_FOREGROUND:
app.inForeground = reader.nextBooleanOrNull();
break;
default:
if (unknown == null) {
unknown = new ConcurrentHashMap<>();
Expand Down
Loading

0 comments on commit 4edebf2

Please sign in to comment.