Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### Features

- Create onCreate and onStart spans for all Activities ([#4025](https://github.com/getsentry/sentry-java/pull/4025))
- Add split apks info to the `App` context ([#3193](https://github.com/getsentry/sentry-java/pull/3193)))

### Fixes

Expand Down
1 change: 1 addition & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ public final class io/sentry/android/core/DeviceInfoUtil {
public static fun getInstance (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/DeviceInfoUtil;
public fun getOperatingSystem ()Lio/sentry/protocol/OperatingSystem;
public fun getSideLoadedInfo ()Lio/sentry/android/core/ContextUtils$SideLoadedInfo;
public fun getSplitApksInfo ()Lio/sentry/android/core/ContextUtils$SplitApksInfo;
public fun getTotalMemory ()Ljava/lang/Long;
public static fun isCharging (Landroid/content/Intent;Lio/sentry/SentryOptions;)Ljava/lang/Boolean;
public static fun resetInstance ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,19 @@ private void setApp(final @NotNull SentryBaseEvent event, final @NotNull Object
}
}

try {
final ContextUtils.SplitApksInfo splitApksInfo =
DeviceInfoUtil.getInstance(context, options).getSplitApksInfo();
if (splitApksInfo != null) {
app.setSplitApks(splitApksInfo.isSplitApks());
if (splitApksInfo.getSplitNames() != null) {
app.setSplitNames(Arrays.asList(splitApksInfo.getSplitNames()));
}
}
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Error getting split apks info.", e);
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
Expand Down Expand Up @@ -63,6 +64,27 @@ public boolean isSideLoaded() {
}
}

static class SplitApksInfo {
// https://github.com/google/bundletool/blob/master/src/main/java/com/android/tools/build/bundletool/model/AndroidManifest.java#L257-L263
static final String SPLITS_REQUIRED = "com.android.vending.splits.required";

private final boolean isSplitApks;
private final String[] splitNames;

public SplitApksInfo(final boolean isSplitApks, final String[] splitNames) {
this.isSplitApks = isSplitApks;
this.splitNames = splitNames;
}

public boolean isSplitApks() {
return isSplitApks;
}

public @Nullable String[] getSplitNames() {
return splitNames;
}
}

private ContextUtils() {}

// to avoid doing a bunch of Binder calls we use LazyEvaluator to cache the values that are static
Expand Down Expand Up @@ -322,6 +344,26 @@ public static boolean isForegroundImportance() {
return null;
}

@SuppressWarnings({"deprecation"})
static @Nullable SplitApksInfo retrieveSplitApksInfo(
final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) {
String[] splitNames = null;
final ApplicationInfo applicationInfo = getApplicationInfo(context, buildInfoProvider);
final PackageInfo packageInfo = getPackageInfo(context, buildInfoProvider);

if (packageInfo != null) {
splitNames = packageInfo.splitNames;
boolean isSplitApks = false;
if (applicationInfo != null && applicationInfo.metaData != null) {
isSplitApks = applicationInfo.metaData.getBoolean(SplitApksInfo.SPLITS_REQUIRED);
}

return new SplitApksInfo(isSplitApks, splitNames);
}

return null;
}

/**
* Get the human-facing Application name.
*
Expand Down Expand Up @@ -422,6 +464,7 @@ public static boolean isForegroundImportance() {
static void setAppPackageInfo(
final @NotNull PackageInfo packageInfo,
final @NotNull BuildInfoProvider buildInfoProvider,
final @Nullable DeviceInfoUtil deviceInfoUtil,
final @NotNull App app) {
app.setAppIdentifier(packageInfo.packageName);
app.setAppVersion(packageInfo.versionName);
Expand All @@ -446,6 +489,19 @@ static void setAppPackageInfo(
}
}
app.setPermissions(permissions);

if (deviceInfoUtil != null) {
try {
final ContextUtils.SplitApksInfo splitApksInfo = deviceInfoUtil.getSplitApksInfo();
if (splitApksInfo != null) {
app.setSplitApks(splitApksInfo.isSplitApks());
if (splitApksInfo.getSplitNames() != null) {
app.setSplitNames(Arrays.asList(splitApksInfo.getSplitNames()));
}
}
} catch (Throwable e) {
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,15 @@ private void setPackageInfo(final @NotNull SentryBaseEvent event, final @NotNull
String versionCode = ContextUtils.getVersionCode(packageInfo, buildInfoProvider);

setDist(event, versionCode);
ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, app);

@Nullable DeviceInfoUtil deviceInfoUtil = null;
try {
deviceInfoUtil = this.deviceInfoUtil.get();
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e);
}

ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, deviceInfoUtil, app);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public final class DeviceInfoUtil {
private final @NotNull BuildInfoProvider buildInfoProvider;
private final @Nullable Boolean isEmulator;
private final @Nullable ContextUtils.SideLoadedInfo sideLoadedInfo;
private final @Nullable ContextUtils.SplitApksInfo splitApksInfo;
private final @NotNull OperatingSystem os;

private final @Nullable Long totalMem;
Expand All @@ -65,6 +66,7 @@ public DeviceInfoUtil(
isEmulator = buildInfoProvider.isEmulator();
sideLoadedInfo =
ContextUtils.retrieveSideLoadedInfo(context, options.getLogger(), buildInfoProvider);
splitApksInfo = ContextUtils.retrieveSplitApksInfo(context, buildInfoProvider);
final @Nullable ActivityManager.MemoryInfo memInfo =
ContextUtils.getMemInfo(context, options.getLogger());
if (memInfo != null) {
Expand Down Expand Up @@ -188,6 +190,11 @@ public ContextUtils.SideLoadedInfo getSideLoadedInfo() {
return sideLoadedInfo;
}

@Nullable
public ContextUtils.SplitApksInfo getSplitApksInfo() {
return splitApksInfo;
}

private void setDeviceIO(final @NotNull Device device, final boolean includeDynamicData) {
final Intent batteryIntent = getBatteryIntent();
if (batteryIntent != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public static Map<String, Object> serializeScope(
ContextUtils.getPackageInfo(
context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider);
if (packageInfo != null) {
ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, app);
ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, deviceInfoUtil, app);
}
scope.getContexts().setApp(app);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import android.app.ActivityManager.RunningAppProcessInfo
import android.content.BroadcastReceiver
import android.content.Context
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Process
import androidx.test.core.app.ApplicationProvider
Expand All @@ -26,6 +29,7 @@ import org.robolectric.shadows.ShadowActivityManager
import org.robolectric.shadows.ShadowBuild
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
Expand Down Expand Up @@ -116,6 +120,38 @@ class ContextUtilsTest {
assertEquals("play.google.com", sideLoadedInfo.installerStore)
}

@Test
fun `given a valid PackageInfo, returns valid splitNames`() {
val splitNames = arrayOf<String?>("config.arm64_v8a")
val mockedContext = mock<Context>()
val mockedPackageManager = mock<PackageManager>()
val mockedApplicationInfo = mock<ApplicationInfo>()
val mockedPackageInfo = mock<PackageInfo>()
mockedPackageInfo.splitNames = splitNames

whenever(mockedContext.packageName).thenReturn("dummy")

whenever(
mockedPackageManager.getApplicationInfo(
any<String>(),
any<PackageManager.ApplicationInfoFlags>()
)
).thenReturn(mockedApplicationInfo)

whenever(
mockedPackageManager.getPackageInfo(
any<String>(),
any<PackageManager.PackageInfoFlags>()
)
).thenReturn(mockedPackageInfo)

whenever(mockedContext.packageManager).thenReturn(mockedPackageManager)

val splitApksInfo =
ContextUtils.retrieveSplitApksInfo(mockedContext, BuildInfoProvider(logger))
assertContentEquals(splitNames, splitApksInfo!!.splitNames)
}

@Test
@Config(qualifiers = "w360dp-h640dp-xxhdpi")
fun `when display metrics specified, getDisplayMetrics returns correct values`() {
Expand Down
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -4333,6 +4333,8 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
public fun getDeviceAppHash ()Ljava/lang/String;
public fun getInForeground ()Ljava/lang/Boolean;
public fun getPermissions ()Ljava/util/Map;
public fun getSplitApks ()Ljava/lang/Boolean;
public fun getSplitNames ()Ljava/util/List;
public fun getStartType ()Ljava/lang/String;
public fun getUnknown ()Ljava/util/Map;
public fun getViewNames ()Ljava/util/List;
Expand All @@ -4347,6 +4349,8 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
public fun setDeviceAppHash (Ljava/lang/String;)V
public fun setInForeground (Ljava/lang/Boolean;)V
public fun setPermissions (Ljava/util/Map;)V
public fun setSplitApks (Ljava/lang/Boolean;)V
public fun setSplitNames (Ljava/util/List;)V
public fun setStartType (Ljava/lang/String;)V
public fun setUnknown (Ljava/util/Map;)V
public fun setViewNames (Ljava/util/List;)V
Expand All @@ -4368,6 +4372,8 @@ public final class io/sentry/protocol/App$JsonKeys {
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 static final field IS_SPLIT_APKS Ljava/lang/String;
public static final field SPLIT_NAMES Ljava/lang/String;
public static final field START_TYPE Ljava/lang/String;
public static final field VIEW_NAMES Ljava/lang/String;
public fun <init> ()V
Expand Down
47 changes: 45 additions & 2 deletions sentry/src/main/java/io/sentry/protocol/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public final class App implements JsonUnknown, JsonSerializable {
* visible to the user.
*/
private @Nullable Boolean inForeground;
/** A flag indicating whether the app is split into multiple APKs */
private @Nullable Boolean isSplitApks;
/* The list of split APKs */
private @Nullable List<String> splitNames;

public App() {}

Expand All @@ -64,6 +68,8 @@ public App() {}
this.inForeground = app.inForeground;
this.viewNames = CollectionUtils.newArrayList(app.viewNames);
this.startType = app.startType;
this.isSplitApks = app.isSplitApks;
this.splitNames = app.splitNames;
this.unknown = CollectionUtils.newConcurrentHashMap(app.unknown);
}

Expand Down Expand Up @@ -163,6 +169,22 @@ public void setStartType(final @Nullable String startType) {
this.startType = startType;
}

public @Nullable Boolean getSplitApks() {
return isSplitApks;
}

public void setSplitApks(final @Nullable Boolean splitApks) {
isSplitApks = splitApks;
}

public @Nullable List<String> getSplitNames() {
return splitNames;
}

public void setSplitNames(final @Nullable List<String> splitNames) {
this.splitNames = splitNames;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -178,7 +200,9 @@ public boolean equals(Object o) {
&& Objects.equals(permissions, app.permissions)
&& Objects.equals(inForeground, app.inForeground)
&& Objects.equals(viewNames, app.viewNames)
&& Objects.equals(startType, app.startType);
&& Objects.equals(startType, app.startType)
&& Objects.equals(isSplitApks, app.isSplitApks)
&& Objects.equals(splitNames, app.splitNames);
}

@Override
Expand All @@ -194,7 +218,9 @@ public int hashCode() {
permissions,
inForeground,
viewNames,
startType);
startType,
isSplitApks,
splitNames);
}

// region json
Expand Down Expand Up @@ -222,6 +248,8 @@ public static final class JsonKeys {
public static final String IN_FOREGROUND = "in_foreground";
public static final String VIEW_NAMES = "view_names";
public static final String START_TYPE = "start_type";
public static final String IS_SPLIT_APKS = "is_split_apks";
public static final String SPLIT_NAMES = "split_names";
}

@Override
Expand Down Expand Up @@ -261,6 +289,12 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
if (startType != null) {
writer.name(JsonKeys.START_TYPE).value(startType);
}
if (isSplitApks != null) {
writer.name(JsonKeys.IS_SPLIT_APKS).value(isSplitApks);
}
if (splitNames != null && !splitNames.isEmpty()) {
writer.name(JsonKeys.SPLIT_NAMES).value(logger, splitNames);
}
if (unknown != null) {
for (String key : unknown.keySet()) {
Object value = unknown.get(key);
Expand Down Expand Up @@ -319,6 +353,15 @@ public static final class Deserializer implements JsonDeserializer<App> {
case JsonKeys.START_TYPE:
app.startType = reader.nextStringOrNull();
break;
case JsonKeys.IS_SPLIT_APKS:
app.isSplitApks = reader.nextBooleanOrNull();
break;
case JsonKeys.SPLIT_NAMES:
final @Nullable List<String> splitNames = (List<String>) reader.nextObjectOrNull();
if (splitNames != null) {
app.setSplitNames(splitNames);
}
break;
default:
if (unknown == null) {
unknown = new ConcurrentHashMap<>();
Expand Down
Loading