Skip to content

Commit 99bdeb7

Browse files
authored
Merge fa15544 into 25f1ca4
2 parents 25f1ca4 + fa15544 commit 99bdeb7

File tree

10 files changed

+177
-20
lines changed

10 files changed

+177
-20
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public final class io/sentry/android/core/BuildInfoProvider {
158158
}
159159

160160
public final class io/sentry/android/core/ContextUtils {
161-
public static fun isForegroundImportance ()Z
161+
public static fun isForegroundImportance (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;)Z
162162
}
163163

164164
public class io/sentry/android/core/CurrentActivityHolder {
@@ -445,6 +445,7 @@ public class io/sentry/android/core/performance/AppStartMetrics {
445445
public static fun onApplicationPostCreate (Landroid/app/Application;)V
446446
public static fun onContentProviderCreate (Landroid/content/ContentProvider;)V
447447
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
448+
public fun setAppLaunchedInForeground (Z)V
448449
public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V
449450
public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
450451
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ private void startTracing(final @NotNull Activity activity) {
167167

168168
// we only track app start for processes that will show an Activity (full launch).
169169
// Here we check the process importance which will tell us that.
170-
final boolean foregroundImportance = ContextUtils.isForegroundImportance();
170+
final boolean foregroundImportance =
171+
ContextUtils.isForegroundImportance(activity, buildInfoProvider);
171172
if (foregroundImportance && appStartTimeSpan.hasStarted()) {
172173
appStartTime = appStartTimeSpan.getStartTimestamp();
173174
coldStart =

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

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.sentry.android.core;
22

3-
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
43
import static android.content.Context.ACTIVITY_SERVICE;
54
import static android.content.Context.RECEIVER_EXPORTED;
65
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
@@ -15,6 +14,7 @@
1514
import android.content.pm.PackageInfo;
1615
import android.content.pm.PackageManager;
1716
import android.os.Build;
17+
import android.os.PowerManager;
1818
import android.provider.Settings;
1919
import android.util.DisplayMetrics;
2020
import io.sentry.ILogger;
@@ -26,6 +26,7 @@
2626
import java.io.FileReader;
2727
import java.io.IOException;
2828
import java.util.HashMap;
29+
import java.util.List;
2930
import java.util.Map;
3031
import org.jetbrains.annotations.ApiStatus;
3132
import org.jetbrains.annotations.NotNull;
@@ -161,22 +162,77 @@ static String getVersionName(final @NotNull PackageInfo packageInfo) {
161162
return Integer.toString(packageInfo.versionCode);
162163
}
163164

165+
/*
166+
* https://github.com/firebase/firebase-android-sdk/blob/58540de24c9b1eb7780c9f642c2cf17478e65734/firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java#L497
167+
*
168+
* Copyright 2022 Google LLC
169+
*
170+
* Licensed under the Apache License, Version 2.0 (the "License");
171+
* you may not use this file except in compliance with the License.
172+
* You may obtain a copy of the License at
173+
*
174+
* http://www.apache.org/licenses/LICENSE-2.0
175+
*
176+
* Unless required by applicable law or agreed to in writing, software
177+
* distributed under the License is distributed on an "AS IS" BASIS,
178+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
179+
* See the License for the specific language governing permissions and
180+
* limitations under the License.
181+
*/
164182
/**
165183
* Check if the Started process has IMPORTANCE_FOREGROUND importance which means that the process
166184
* will start an Activity.
167185
*
168186
* @return true if IMPORTANCE_FOREGROUND and false otherwise
169187
*/
170-
@ApiStatus.Internal
171-
public static boolean isForegroundImportance() {
172-
try {
173-
final ActivityManager.RunningAppProcessInfo appProcessInfo =
174-
new ActivityManager.RunningAppProcessInfo();
175-
ActivityManager.getMyMemoryState(appProcessInfo);
176-
return appProcessInfo.importance == IMPORTANCE_FOREGROUND;
177-
} catch (Throwable ignored) {
178-
// should never happen
188+
@SuppressLint("NewApi")
189+
@SuppressWarnings("deprecation")
190+
public static boolean isForegroundImportance(
191+
final @NotNull Context appContext, final @NotNull BuildInfoProvider buildInfoProvider) {
192+
193+
// Do not call ProcessStats.getActivityManger, caching will break tests that indirectly depend
194+
// on ProcessStats.
195+
ActivityManager activityManager =
196+
(ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
197+
if (activityManager == null) {
198+
return true;
199+
}
200+
List<ActivityManager.RunningAppProcessInfo> appProcesses =
201+
activityManager.getRunningAppProcesses();
202+
if (appProcesses != null) {
203+
String appProcessName = appContext.getPackageName();
204+
String allowedAppProcessNamePrefix = appProcessName + ":";
205+
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
206+
if (appProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
207+
continue;
208+
}
209+
if (appProcess.processName.equals(appProcessName)
210+
|| appProcess.processName.startsWith(allowedAppProcessNamePrefix)) {
211+
boolean isAppInForeground = true;
212+
213+
// For the case when the app is in foreground and the device transitions to sleep mode,
214+
// the importance of the process is set to IMPORTANCE_TOP_SLEEPING. However, this
215+
// importance level was introduced in M. Pre M, the process importance is not changed to
216+
// IMPORTANCE_TOP_SLEEPING when the display turns off. So we need to rely also on the
217+
// state of the display to decide if any app process is really visible.
218+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.M) {
219+
PowerManager powerManager =
220+
(PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
221+
if (powerManager != null) {
222+
isAppInForeground =
223+
buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.KITKAT_WATCH
224+
? powerManager.isInteractive()
225+
: powerManager.isScreenOn();
226+
}
227+
}
228+
229+
if (isAppInForeground) {
230+
return true;
231+
}
232+
}
233+
}
179234
}
235+
180236
return false;
181237
}
182238

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public static synchronized void init(
8787
@NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> configuration) {
8888

8989
try {
90+
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
9091
Sentry.init(
9192
OptionsContainer.create(SentryAndroidOptions.class),
9293
options -> {
@@ -103,7 +104,6 @@ public static synchronized void init(
103104
(isTimberUpstreamAvailable
104105
&& classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));
105106

106-
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
107107
final LoadClass loadClass = new LoadClass();
108108
final ActivityFramesTracker activityFramesTracker =
109109
new ActivityFramesTracker(loadClass, options);
@@ -148,7 +148,8 @@ public static synchronized void init(
148148
true);
149149

150150
final @NotNull IHub hub = Sentry.getCurrentHub();
151-
if (hub.getOptions().isEnableAutoSessionTracking() && ContextUtils.isForegroundImportance()) {
151+
if (hub.getOptions().isEnableAutoSessionTracking()
152+
&& ContextUtils.isForegroundImportance(context, buildInfoProvider)) {
152153
// The LifecycleWatcher of AppLifecycleIntegration may already started a session
153154
// so only start a session if it's not already started
154155
// This e.g. happens on React Native, or e.g. on deferred SDK init

sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
import android.content.ContentProvider;
55
import android.os.SystemClock;
66
import androidx.annotation.Nullable;
7+
import androidx.annotation.VisibleForTesting;
78
import io.sentry.ITransactionProfiler;
89
import io.sentry.TracesSamplingDecision;
10+
import io.sentry.android.core.AndroidLogger;
11+
import io.sentry.android.core.BuildInfoProvider;
912
import io.sentry.android.core.ContextUtils;
1013
import io.sentry.android.core.SentryAndroidOptions;
1114
import java.util.ArrayList;
1215
import java.util.Collections;
1316
import java.util.HashMap;
1417
import java.util.List;
1518
import java.util.Map;
19+
import java.util.concurrent.TimeUnit;
1620
import org.jetbrains.annotations.ApiStatus;
1721
import org.jetbrains.annotations.NotNull;
1822
import org.jetbrains.annotations.TestOnly;
@@ -102,6 +106,11 @@ public boolean isAppLaunchedInForeground() {
102106
return appLaunchedInForeground;
103107
}
104108

109+
@VisibleForTesting
110+
public void setAppLaunchedInForeground(boolean appLaunchedInForeground) {
111+
this.appLaunchedInForeground = appLaunchedInForeground;
112+
}
113+
105114
/**
106115
* Provides all collected content provider onCreate time spans
107116
*
@@ -137,12 +146,27 @@ public long getClassLoadedUptimeMs() {
137146
// Only started when sdk version is >= N
138147
final @NotNull TimeSpan appStartSpan = getAppStartTimeSpan();
139148
if (appStartSpan.hasStarted()) {
140-
return appStartSpan;
149+
return validateAppStartSpan(appStartSpan);
141150
}
142151
}
143152

144153
// fallback: use sdk init time span, as it will always have a start time set
145-
return getSdkInitTimeSpan();
154+
return validateAppStartSpan(getSdkInitTimeSpan());
155+
}
156+
157+
private @NotNull TimeSpan validateAppStartSpan(final @NotNull TimeSpan appStartSpan) {
158+
long spanStartMillis = appStartSpan.getStartTimestampMs();
159+
long spanEndMillis =
160+
appStartSpan.hasStopped()
161+
? appStartSpan.getProjectedStopTimestampMs()
162+
: SystemClock.uptimeMillis();
163+
long durationMillis = spanEndMillis - spanStartMillis;
164+
// If the app was launched more than 1 minute ago or it was launched in the background we return
165+
// an empty span, as the app start will be wrong
166+
if (durationMillis > TimeUnit.MINUTES.toMillis(1) || !isAppLaunchedInForeground()) {
167+
return new TimeSpan();
168+
}
169+
return appStartSpan;
146170
}
147171

148172
@TestOnly
@@ -195,7 +219,9 @@ public static void onApplicationCreate(final @NotNull Application application) {
195219
final @NotNull AppStartMetrics instance = getInstance();
196220
if (instance.applicationOnCreate.hasNotStarted()) {
197221
instance.applicationOnCreate.setStartedAt(now);
198-
instance.appLaunchedInForeground = ContextUtils.isForegroundImportance();
222+
instance.appLaunchedInForeground =
223+
ContextUtils.isForegroundImportance(
224+
application, new BuildInfoProvider(new AndroidLogger()));
199225
}
200226
}
201227

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class ActivityLifecycleIntegrationTest {
9494

9595
whenever(hub.options).thenReturn(options)
9696

97+
AppStartMetrics.getInstance().isAppLaunchedInForeground = true
9798
// We let the ActivityLifecycleIntegration create the proper transaction here
9899
val optionCaptor = argumentCaptor<TransactionOptions>()
99100
val contextCaptor = argumentCaptor<TransactionContext>()

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class ContextUtilsTest {
3838
private lateinit var shadowActivityManager: ShadowActivityManager
3939
private lateinit var context: Context
4040
private lateinit var logger: ILogger
41+
private val buildInfoProvider = mock<BuildInfoProvider>()
4142

4243
@BeforeTest
4344
fun `set up`() {
@@ -46,6 +47,7 @@ class ContextUtilsTest {
4647
ShadowBuild.reset()
4748
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager?
4849
shadowActivityManager = Shadow.extract(activityManager)
50+
whenever(buildInfoProvider.sdkInfoVersion).thenReturn(Build.VERSION_CODES.TIRAMISU)
4951
}
5052

5153
@Test
@@ -197,7 +199,7 @@ class ContextUtilsTest {
197199

198200
@Test
199201
fun `returns true when app started with foreground importance`() {
200-
assertTrue(ContextUtils.isForegroundImportance())
202+
assertTrue(ContextUtils.isForegroundImportance(context, buildInfoProvider))
201203
}
202204

203205
@Test
@@ -211,6 +213,6 @@ class ContextUtilsTest {
211213
}
212214
)
213215
)
214-
assertFalse(ContextUtils.isForegroundImportance())
216+
assertFalse(ContextUtils.isForegroundImportance(context, buildInfoProvider))
215217
}
216218
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class PerformanceAndroidEventProcessorTest {
4646
tracesSampleRate: Double? = 1.0,
4747
enablePerformanceV2: Boolean = false
4848
): PerformanceAndroidEventProcessor {
49+
AppStartMetrics.getInstance().isAppLaunchedInForeground = true
4950
options.tracesSampleRate = tracesSampleRate
5051
options.isEnablePerformanceV2 = enablePerformanceV2
5152
whenever(hub.options).thenReturn(options)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ class SentryAndroidTest {
339339
val context = ContextUtilsTestHelper.createMockContext()
340340

341341
Mockito.mockStatic(ContextUtils::class.java).use { mockedContextUtils ->
342-
mockedContextUtils.`when`<Any> { ContextUtils.isForegroundImportance() }
342+
mockedContextUtils.`when`<Any> { ContextUtils.isForegroundImportance(any(), any()) }
343343
.thenReturn(inForeground)
344344
SentryAndroid.init(context) { options ->
345345
options.release = "prod"

sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import org.junit.Before
1010
import org.junit.runner.RunWith
1111
import org.mockito.kotlin.mock
1212
import org.robolectric.annotation.Config
13+
import java.util.concurrent.TimeUnit
1314
import kotlin.test.Test
1415
import kotlin.test.assertEquals
16+
import kotlin.test.assertFalse
1517
import kotlin.test.assertNotEquals
1618
import kotlin.test.assertNull
1719
import kotlin.test.assertSame
@@ -28,6 +30,7 @@ class AppStartMetricsTest {
2830
fun setup() {
2931
AppStartMetrics.getInstance().clear()
3032
SentryShadowProcess.setStartUptimeMillis(42)
33+
AppStartMetrics.getInstance().isAppLaunchedInForeground = true
3134
}
3235

3336
@Test
@@ -106,4 +109,69 @@ class AppStartMetricsTest {
106109
fun `class load time is set`() {
107110
assertNotEquals(0, AppStartMetrics.getInstance().classLoadedUptimeMs)
108111
}
112+
113+
@Test
114+
fun `if app is launched in background, appStartTimeSpanWithFallback returns an empty span`() {
115+
AppStartMetrics.getInstance().isAppLaunchedInForeground = false
116+
val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan
117+
appStartTimeSpan.start()
118+
assertTrue(appStartTimeSpan.hasStarted())
119+
120+
val options = SentryAndroidOptions().apply {
121+
isEnablePerformanceV2 = false
122+
}
123+
124+
val timeSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options)
125+
assertFalse(timeSpan.hasStarted())
126+
}
127+
128+
@Test
129+
fun `if app is launched in background with perfV2, appStartTimeSpanWithFallback returns an empty span`() {
130+
AppStartMetrics.getInstance().isAppLaunchedInForeground = false
131+
val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan
132+
appStartTimeSpan.start()
133+
assertTrue(appStartTimeSpan.hasStarted())
134+
135+
val options = SentryAndroidOptions().apply {
136+
isEnablePerformanceV2 = true
137+
}
138+
139+
val timeSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options)
140+
assertFalse(timeSpan.hasStarted())
141+
}
142+
143+
@Test
144+
fun `if app start span is at most 1 minute, appStartTimeSpanWithFallback returns the app start span`() {
145+
val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan
146+
appStartTimeSpan.start()
147+
appStartTimeSpan.stop()
148+
appStartTimeSpan.setStartedAt(1)
149+
appStartTimeSpan.setStoppedAt(TimeUnit.MINUTES.toMillis(1) + 1)
150+
assertTrue(appStartTimeSpan.hasStarted())
151+
152+
val options = SentryAndroidOptions().apply {
153+
isEnablePerformanceV2 = true
154+
}
155+
156+
val timeSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options)
157+
assertTrue(timeSpan.hasStarted())
158+
assertSame(appStartTimeSpan, timeSpan)
159+
}
160+
161+
@Test
162+
fun `if app start span is longer than 1 minute, appStartTimeSpanWithFallback returns an empty span`() {
163+
val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan
164+
appStartTimeSpan.start()
165+
appStartTimeSpan.stop()
166+
appStartTimeSpan.setStartedAt(1)
167+
appStartTimeSpan.setStoppedAt(TimeUnit.MINUTES.toMillis(1) + 2)
168+
assertTrue(appStartTimeSpan.hasStarted())
169+
170+
val options = SentryAndroidOptions().apply {
171+
isEnablePerformanceV2 = true
172+
}
173+
174+
val timeSpan = AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options)
175+
assertFalse(timeSpan.hasStarted())
176+
}
109177
}

0 commit comments

Comments
 (0)