Skip to content

Commit 4998f4f

Browse files
authored
Add process details to Crashlytics events (#5445)
Add process details to Crashlytics java and anr events. NDK events will be handled later after we upgrade crashpad. Also fixed a bug in detecting background processes. The proto change was made in internal cl/574516195
1 parent c259e01 commit 4998f4f

File tree

8 files changed

+239
-40
lines changed

8 files changed

+239
-40
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22

3+
* [changed] Include more details about app processes in reports.
34

45
# 18.5.0
56
* [changed] Added Kotlin extensions (KTX) APIs from `com.google.firebase:firebase-crashlytics-ktx`
@@ -520,4 +521,3 @@ The following release notes describe changes in the new SDK.
520521
from your `AndroidManifest.xml` file.
521522
* [removed] The `fabric.properties` and `crashlytics.properties` files are no
522523
longer supported. Remove them from your app.
523-

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CommonUtilsTest.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.mockito.Mockito.mock;
2020
import static org.mockito.Mockito.when;
2121

22-
import android.app.ActivityManager.RunningAppProcessInfo;
2322
import android.content.Context;
2423
import android.content.pm.ApplicationInfo;
2524
import android.content.pm.PackageManager;
@@ -139,18 +138,6 @@ public void testGetTotalRamInBytes() {
139138
Log.d(Logger.TAG, "testGetTotalRam: " + bytes);
140139
}
141140

142-
public void testGetAppProcessInfo() {
143-
final Context context = getContext();
144-
RunningAppProcessInfo info = CommonUtils.getAppProcessInfo(context.getPackageName(), context);
145-
assertNotNull(info);
146-
// It is not possible to test the state of info.importance because the value is not
147-
// always the same under test as it is when the sdk is running in an app. In API 21, the
148-
// importance under test started returning VISIBLE instead of FOREGROUND.
149-
150-
info = CommonUtils.getAppProcessInfo("nonexistant.package.name", context);
151-
assertNull(info);
152-
}
153-
154141
public void testIsRooted() {
155142
// No good way to test the alternate case,
156143
// just want to ensure we can complete the call without an exception here.

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
package com.google.firebase.crashlytics.internal.persistence;
1616

17+
import static com.google.common.truth.Truth.assertThat;
1718
import static org.junit.Assert.*;
1819
import static org.mockito.ArgumentMatchers.anyString;
1920
import static org.mockito.Mockito.mock;
2021
import static org.mockito.Mockito.when;
2122

23+
import androidx.annotation.Nullable;
2224
import com.google.firebase.crashlytics.internal.CrashlyticsTestCase;
2325
import com.google.firebase.crashlytics.internal.common.CrashlyticsAppQualitySessionsSubscriber;
2426
import com.google.firebase.crashlytics.internal.common.CrashlyticsReportWithSessionId;
@@ -29,6 +31,7 @@
2931
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.Execution;
3032
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.Execution.Signal;
3133
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.Execution.Thread.Frame;
34+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.ProcessDetails;
3235
import com.google.firebase.crashlytics.internal.model.ImmutableList;
3336
import com.google.firebase.crashlytics.internal.settings.Settings;
3437
import com.google.firebase.crashlytics.internal.settings.Settings.FeatureFlagData;
@@ -442,6 +445,31 @@ public void testLoadFinalizedReports_reportWithUserId_returnsReportWithProperUse
442445
assertEquals(userId, finalizedReport.getSession().getUser().getIdentifier());
443446
}
444447

448+
public void testLoadFinalizedReports_reportWithProcessDetails_returnsReportWithProcessDetails() {
449+
String sessionId = "testSession";
450+
CrashlyticsReport testReport = makeTestReport(sessionId);
451+
ProcessDetails process1 = makeProcessDetails("process1");
452+
ProcessDetails process2 = makeProcessDetails("process2");
453+
CrashlyticsReport.Session.Event testEvent =
454+
makeTestEvent(
455+
"java.lang.Exception", "reason", process1, ImmutableList.from(process1, process2));
456+
457+
reportPersistence.persistReport(testReport);
458+
reportPersistence.persistEvent(testEvent, sessionId);
459+
reportPersistence.finalizeReports(null, 0L);
460+
461+
List<CrashlyticsReportWithSessionId> finalizedReports =
462+
reportPersistence.loadFinalizedReports();
463+
464+
assertThat(finalizedReports).hasSize(1);
465+
CrashlyticsReport finalizedReport = finalizedReports.get(0).getReport();
466+
assertThat(finalizedReport.getSession()).isNotNull();
467+
assertThat(finalizedReport.getSession().getEvents()).isNotNull();
468+
Event event = finalizedReport.getSession().getEvents().get(0);
469+
assertThat(event.getApp().getCurrentProcessDetails()).isEqualTo(process1);
470+
assertThat(event.getApp().getAppProcessDetails()).containsExactly(process1, process2);
471+
}
472+
445473
public void
446474
testLoadFinalizedReports_reportsWithUserIdInMultipleSessions_returnsReportsWithProperUserIds() {
447475
final String userId1 = "testUser1";
@@ -853,12 +881,23 @@ private static Event makeTestEvent() {
853881
}
854882

855883
private static Event makeTestEvent(String type, String reason) {
884+
return makeTestEvent(
885+
type, reason, /* currentProcessDetails= */ null, /* appProcessDetails= */ null);
886+
}
887+
888+
private static Event makeTestEvent(
889+
String type,
890+
String reason,
891+
@Nullable ProcessDetails currentProcessDetails,
892+
@Nullable ImmutableList<ProcessDetails> appProcessDetails) {
856893
return Event.builder()
857894
.setType(type)
858895
.setTimestamp(1000)
859896
.setApp(
860897
Session.Event.Application.builder()
861898
.setBackground(false)
899+
.setCurrentProcessDetails(currentProcessDetails)
900+
.setAppProcessDetails(appProcessDetails)
862901
.setExecution(
863902
Execution.builder()
864903
.setBinaries(
@@ -977,4 +1016,13 @@ private static CrashlyticsReport.ApplicationExitInfo makeAppExitInfo() {
9771016
.setRss(1L)
9781017
.build();
9791018
}
1019+
1020+
private static ProcessDetails makeProcessDetails(String processName) {
1021+
return ProcessDetails.builder()
1022+
.setProcessName(processName)
1023+
.setPid(0)
1024+
.setImportance(0)
1025+
.setDefaultProcess(false)
1026+
.build();
1027+
}
9801028
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal
18+
19+
import android.app.ActivityManager
20+
import android.content.Context
21+
import android.os.Build
22+
import android.os.Process
23+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.ProcessDetails
24+
import com.google.firebase.crashlytics.internal.model.ImmutableList
25+
26+
/**
27+
* Provider of ProcessDetails.
28+
*
29+
* @hide
30+
*/
31+
internal object ProcessDetailsProvider {
32+
/** Gets the details of all running app processes. */
33+
fun getAppProcessDetails(context: Context): ImmutableList<ProcessDetails> {
34+
val defaultProcessName = context.applicationInfo.processName
35+
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
36+
val runningAppProcesses = activityManager?.runningAppProcesses ?: listOf()
37+
38+
return ImmutableList.from(
39+
runningAppProcesses.filterNotNull().map { runningAppProcessInfo ->
40+
ProcessDetails.builder()
41+
.setProcessName(runningAppProcessInfo.processName)
42+
.setPid(runningAppProcessInfo.pid)
43+
.setImportance(runningAppProcessInfo.importance)
44+
.setDefaultProcess(runningAppProcessInfo.processName == defaultProcessName)
45+
.build()
46+
}
47+
)
48+
}
49+
50+
/**
51+
* Gets the current process details.
52+
*
53+
* If the current process details are not found for whatever reason, returns process details with
54+
* just the current process name and pid set.
55+
*/
56+
fun getCurrentProcessDetails(context: Context): ProcessDetails {
57+
val pid = Process.myPid()
58+
return getAppProcessDetails(context).find { processDetails -> processDetails.pid == pid }
59+
?: buildProcessDetails(getProcessName(), pid)
60+
}
61+
62+
/** Builds a ProcessDetails object. */
63+
@JvmOverloads
64+
fun buildProcessDetails(
65+
processName: String,
66+
pid: Int = 0,
67+
importance: Int = 0,
68+
isDefaultProcess: Boolean = false
69+
) =
70+
ProcessDetails.builder()
71+
.setProcessName(processName)
72+
.setPid(pid)
73+
.setImportance(importance)
74+
.setDefaultProcess(isDefaultProcess)
75+
.build()
76+
77+
/** Gets the current process name. If the API is not available, returns an empty string. */
78+
private fun getProcessName(): String =
79+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
80+
Process.myProcessName()
81+
} else {
82+
""
83+
}
84+
}

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -132,28 +132,6 @@ static Architecture getValue() {
132132
}
133133
}
134134

135-
/**
136-
* Returns the RunningAppProcessInfo object for the given package, or null if it cannot be found.
137-
*/
138-
public static ActivityManager.RunningAppProcessInfo getAppProcessInfo(
139-
String packageName, Context context) {
140-
final ActivityManager actman =
141-
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
142-
final List<ActivityManager.RunningAppProcessInfo> processes = actman.getRunningAppProcesses();
143-
ActivityManager.RunningAppProcessInfo procInfo = null;
144-
// According to docs, the result of getRunningAppProcesses can be null instead of empty.
145-
// Yay.
146-
if (processes != null) {
147-
for (ActivityManager.RunningAppProcessInfo info : processes) {
148-
if (info.processName.equals(packageName)) {
149-
procInfo = info;
150-
break;
151-
}
152-
}
153-
}
154-
return procInfo;
155-
}
156-
157135
public static String streamToString(InputStream is) {
158136
// Previous code was running into this: http://code.google.com/p/android/issues/detail?id=14562
159137
// on Android 2.3.3. The below code below does not exhibit that problem.

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsReportDataCapture.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
import android.os.StatFs;
2626
import android.text.TextUtils;
2727
import com.google.firebase.crashlytics.BuildConfig;
28+
import com.google.firebase.crashlytics.internal.ProcessDetailsProvider;
2829
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
2930
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Architecture;
3031
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event;
3132
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.Execution;
3233
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.Execution.BinaryImage;
34+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport.Session.Event.Application.ProcessDetails;
3335
import com.google.firebase.crashlytics.internal.model.ImmutableList;
3436
import com.google.firebase.crashlytics.internal.settings.SettingsProvider;
3537
import com.google.firebase.crashlytics.internal.stacktrace.StackTraceTrimmingStrategy;
@@ -71,6 +73,7 @@ public class CrashlyticsReportDataCapture {
7173
private final AppData appData;
7274
private final StackTraceTrimmingStrategy stackTraceTrimmingStrategy;
7375
private final SettingsProvider settingsProvider;
76+
private final ProcessDetailsProvider processDetailsProvider = ProcessDetailsProvider.INSTANCE;
7477

7578
public CrashlyticsReportDataCapture(
7679
Context context,
@@ -239,17 +242,18 @@ private Event.Application populateEventApplicationData(
239242
int maxChainedExceptions,
240243
boolean includeAllThreads) {
241244
Boolean isBackground = null;
242-
final RunningAppProcessInfo runningAppProcessInfo =
243-
CommonUtils.getAppProcessInfo(appData.packageName, context);
244-
if (runningAppProcessInfo != null) {
245+
ProcessDetails currentProcessDetails = processDetailsProvider.getCurrentProcessDetails(context);
246+
if (currentProcessDetails.getImportance() > 0) {
245247
// Several different types of "background" states, easiest to check for not foreground.
246248
isBackground =
247-
runningAppProcessInfo.importance
249+
currentProcessDetails.getImportance()
248250
!= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
249251
}
250252

251253
return Event.Application.builder()
252254
.setBackground(isBackground)
255+
.setCurrentProcessDetails(currentProcessDetails)
256+
.setAppProcessDetails(processDetailsProvider.getAppProcessDetails(context))
253257
.setUiOrientation(orientation)
254258
.setExecution(
255259
populateExecutionData(
@@ -268,6 +272,7 @@ private Event.Application populateEventApplicationData(
268272

269273
return Event.Application.builder()
270274
.setBackground(isBackground)
275+
.setCurrentProcessDetails(processDetailsFromApplicationExitInfo(applicationExitInfo))
271276
.setUiOrientation(orientation)
272277
.setExecution(populateExecutionData(applicationExitInfo))
273278
.build();
@@ -475,4 +480,13 @@ private static int getDeviceArchitecture() {
475480
private static long ensureNonNegative(long value) {
476481
return value > 0 ? value : 0;
477482
}
483+
484+
/** Builds a ProcessDetails object from the details in applicationExitInfo. */
485+
private ProcessDetails processDetailsFromApplicationExitInfo(
486+
CrashlyticsReport.ApplicationExitInfo applicationExitInfo) {
487+
return processDetailsProvider.buildProcessDetails(
488+
applicationExitInfo.getProcessName(),
489+
applicationExitInfo.getPid(),
490+
applicationExitInfo.getImportance());
491+
}
478492
}

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/model/CrashlyticsReport.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,12 @@ public static Builder builder() {
686686
@Nullable
687687
public abstract Boolean getBackground();
688688

689+
@Nullable
690+
public abstract ProcessDetails getCurrentProcessDetails();
691+
692+
@Nullable
693+
public abstract ImmutableList<ProcessDetails> getAppProcessDetails();
694+
689695
public abstract int getUiOrientation();
690696

691697
@NonNull
@@ -955,6 +961,43 @@ public abstract static class Builder {
955961
}
956962
}
957963

964+
@AutoValue
965+
public abstract static class ProcessDetails {
966+
@NonNull
967+
public abstract String getProcessName();
968+
969+
public abstract int getPid();
970+
971+
public abstract int getImportance();
972+
973+
public abstract boolean isDefaultProcess();
974+
975+
@NonNull
976+
public static Builder builder() {
977+
return new AutoValue_CrashlyticsReport_Session_Event_Application_ProcessDetails
978+
.Builder();
979+
}
980+
981+
/** Builder for {@link ProcessDetails}. */
982+
@AutoValue.Builder
983+
public abstract static class Builder {
984+
@NonNull
985+
public abstract Builder setProcessName(@NonNull String processName);
986+
987+
@NonNull
988+
public abstract Builder setPid(int pid);
989+
990+
@NonNull
991+
public abstract Builder setImportance(int importance);
992+
993+
@NonNull
994+
public abstract Builder setDefaultProcess(boolean isDefaultProcess);
995+
996+
@NonNull
997+
public abstract ProcessDetails build();
998+
}
999+
}
1000+
9581001
/** Builder for {@link Application}. */
9591002
@AutoValue.Builder
9601003
public abstract static class Builder {
@@ -972,6 +1015,13 @@ public abstract Builder setCustomAttributes(
9721015
@NonNull
9731016
public abstract Builder setBackground(@Nullable Boolean value);
9741017

1018+
@NonNull
1019+
public abstract Builder setCurrentProcessDetails(@Nullable ProcessDetails processDetails);
1020+
1021+
@NonNull
1022+
public abstract Builder setAppProcessDetails(
1023+
@Nullable ImmutableList<ProcessDetails> appProcessDetails);
1024+
9751025
@NonNull
9761026
public abstract Builder setUiOrientation(int value);
9771027

0 commit comments

Comments
 (0)