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

Add main flag to threads and in_foreground flag for app contexts #2516

Merged
merged 9 commits into from
Feb 8, 2023
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.getMain() == 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) {
Comment on lines +259 to +260
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running into #2525

// 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);
String[] supportedAbis;
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
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");
// TODO: check if this still makes sense, I found a few threads that the right way would be
// owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED);
// This would be the only state that is responding to the user
AppState.getInstance().setInBackground(false);
}

Expand Down
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 @@ -3336,6 +3339,7 @@ public final class io/sentry/protocol/SentryStackTrace$JsonKeys {
public final class io/sentry/protocol/SentryThread : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
public fun <init> ()V
public fun getId ()Ljava/lang/Long;
public fun getMain ()Ljava/lang/Boolean;
public fun getName ()Ljava/lang/String;
public fun getPriority ()Ljava/lang/Integer;
public fun getStacktrace ()Lio/sentry/protocol/SentryStackTrace;
Expand All @@ -3349,6 +3353,7 @@ public final class io/sentry/protocol/SentryThread : io/sentry/JsonSerializable,
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 @@ -51,6 +56,7 @@ public App() {}
this.deviceAppHash = app.deviceAppHash;
this.permissions = CollectionUtils.newConcurrentHashMap(app.permissions);
this.unknown = CollectionUtils.newConcurrentHashMap(app.unknown);
this.inForeground = app.inForeground;
}

@SuppressWarnings("unused")
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
31 changes: 31 additions & 0 deletions sentry/src/main/java/io/sentry/protocol/SentryThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class SentryThread implements JsonUnknown, JsonSerializable {
private @Nullable Boolean current;
private @Nullable Boolean daemon;
private @Nullable SentryStackTrace stacktrace;
private @Nullable Boolean main;

@SuppressWarnings("unused")
private @Nullable Map<String, Object> unknown;
Expand Down Expand Up @@ -184,6 +185,29 @@ public void setState(final @Nullable String state) {
this.state = state;
}

/**
* If applicable, a flag indicating whether the thread was responsible for rendering the user
* interface. On mobile platforms this is oftentimes referred to as the "main thread" or "ui
* thread".
*
* @return if its the main thread or not
*/
@Nullable
public Boolean getMain() {
return main;
}

/**
* If applicable, a flag indicating whether the thread was responsible for rendering the user
* interface. On mobile platforms this is oftentimes referred to as the "main thread" or "ui
* thread".
*
* @param main if its the main thread or not
*/
public void setMain(final @Nullable Boolean main) {
this.main = main;
}

// region json

@Nullable
Expand All @@ -206,6 +230,7 @@ public static final class JsonKeys {
public static final String CURRENT = "current";
public static final String DAEMON = "daemon";
public static final String STACKTRACE = "stacktrace";
public static final String MAIN = "main";
}

@Override
Expand Down Expand Up @@ -236,6 +261,9 @@ public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger)
if (stacktrace != null) {
writer.name(JsonKeys.STACKTRACE).value(logger, stacktrace);
}
if (main != null) {
writer.name(JsonKeys.MAIN).value(main);
}
if (unknown != null) {
for (String key : unknown.keySet()) {
Object value = unknown.get(key);
Expand Down Expand Up @@ -282,6 +310,9 @@ public static final class Deserializer implements JsonDeserializer<SentryThread>
sentryThread.stacktrace =
reader.nextOrNull(logger, new SentryStackTrace.Deserializer());
break;
case JsonKeys.MAIN:
sentryThread.main = reader.nextBooleanOrNull();
break;
default:
if (unknown == null) {
unknown = new ConcurrentHashMap<>();
Expand Down