diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index ccfe1950016..f1be1674040 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -128,6 +128,9 @@ public final class io/sentry/android/core/BuildInfoProvider { public fun isEmulator ()Ljava/lang/Boolean; } +public final class io/sentry/android/core/ContextUtils { +} + public class io/sentry/android/core/CurrentActivityHolder { public fun clearActivity ()V public fun getActivity ()Landroid/app/Activity; @@ -148,6 +151,16 @@ public final class io/sentry/android/core/CurrentActivityIntegration : android/a public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V } +public class io/sentry/android/core/DeviceInfoUtil { + public fun (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)V + public fun collectDeviceInformation (ZZ)Lio/sentry/protocol/Device; + public static fun getInstance (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/DeviceInfoUtil; + public final fun getOperatingSystem ()Lio/sentry/protocol/OperatingSystem; + public fun getSideLoadedInfo ()Lio/sentry/android/core/ContextUtils$SideLoadedInfo; + public static fun resetInstance ()V + protected fun retrieveOperatingSystemInformation ()Lio/sentry/protocol/OperatingSystem; +} + public abstract class io/sentry/android/core/EnvelopeFileObserverIntegration : io/sentry/Integration, java/io/Closeable { public fun ()V public fun close ()V @@ -160,6 +173,16 @@ public abstract interface class io/sentry/android/core/IDebugImagesLoader { public abstract fun loadDebugImages ()Ljava/util/List; } +public final class io/sentry/android/core/Installation { + public static fun id (Landroid/content/Context;)Ljava/lang/String; +} + +public final class io/sentry/android/core/InternalSentrySdk { + public fun ()V + public static fun getCurrentScope ()Lio/sentry/Scope; + public static fun serializeScope (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/Scope;)Ljava/util/Map; +} + public final class io/sentry/android/core/LoadClass { public fun ()V public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java index 6a4b7edfa1c..703f9e1f80d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java @@ -517,11 +517,12 @@ private void mergeUser(final @NotNull SentryBaseEvent event) { private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) { try { - final Map sideLoadedInfo = - ContextUtils.getSideLoadedInfo(context, options.getLogger(), buildInfoProvider); + final ContextUtils.SideLoadedInfo sideLoadedInfo = + ContextUtils.retrieveSideLoadedInfo(context, options.getLogger(), buildInfoProvider); if (sideLoadedInfo != null) { - for (final Map.Entry entry : sideLoadedInfo.entrySet()) { + final @NotNull Map tags = sideLoadedInfo.asTags(); + for (Map.Entry entry : tags.entrySet()) { event.setTag(entry.getKey(), entry.getValue()); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java index d43dde9471c..94743382ec9 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java @@ -22,10 +22,39 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -final class ContextUtils { +@ApiStatus.Internal +public final class ContextUtils { + + static class SideLoadedInfo { + private final boolean isSideLoaded; + private final @Nullable String installerStore; + + public SideLoadedInfo(boolean isSideLoaded, @Nullable String installerStore) { + this.isSideLoaded = isSideLoaded; + this.installerStore = installerStore; + } + + public boolean isSideLoaded() { + return isSideLoaded; + } + + public @Nullable String getInstallerStore() { + return installerStore; + } + + public @NotNull Map asTags() { + final Map data = new HashMap<>(); + data.put("isSideLoaded", String.valueOf(isSideLoaded)); + if (installerStore != null) { + data.put("installerStore", installerStore); + } + return data; + } + } private ContextUtils() {} @@ -187,8 +216,8 @@ static boolean isForegroundImportance(final @NotNull Context context) { return defaultVersion; } - @SuppressWarnings("deprecation") - static @Nullable Map getSideLoadedInfo( + @SuppressWarnings({"deprecation"}) + static @Nullable SideLoadedInfo retrieveSideLoadedInfo( final @NotNull Context context, final @NotNull ILogger logger, final @NotNull BuildInfoProvider buildInfoProvider) { @@ -202,20 +231,10 @@ static boolean isForegroundImportance(final @NotNull Context context) { // getInstallSourceInfo requires INSTALL_PACKAGES permission which is only given to system // apps. + // if it's installed via adb, system apps or untrusted sources + // could be amazon, google play etc - or null in case of sideload final String installerPackageName = packageManager.getInstallerPackageName(packageName); - - final Map sideLoadedInfo = new HashMap<>(); - - if (installerPackageName != null) { - sideLoadedInfo.put("isSideLoaded", "false"); - // could be amazon, google play etc - sideLoadedInfo.put("installerStore", installerPackageName); - } else { - // if it's installed via adb, system apps or untrusted sources - sideLoadedInfo.put("isSideLoaded", "true"); - } - - return sideLoadedInfo; + return new SideLoadedInfo(installerPackageName == null, installerPackageName); } } catch (IllegalArgumentException e) { // it'll never be thrown as we are querying its own App's package. diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java index 8d88c6674ae..e2e635e1d13 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DefaultAndroidEventProcessor.java @@ -1,22 +1,12 @@ package io.sentry.android.core; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; -import static android.os.BatteryManager.EXTRA_TEMPERATURE; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.os.BatteryManager; import android.os.Build; -import android.os.Environment; -import android.os.LocaleList; -import android.os.StatFs; -import android.os.SystemClock; -import android.util.DisplayMetrics; import io.sentry.DateUtils; import io.sentry.EventProcessor; import io.sentry.Hint; @@ -24,27 +14,16 @@ import io.sentry.SentryEvent; import io.sentry.SentryLevel; import io.sentry.android.core.internal.util.AndroidMainThreadChecker; -import io.sentry.android.core.internal.util.ConnectivityChecker; -import io.sentry.android.core.internal.util.CpuInfoUtils; -import io.sentry.android.core.internal.util.DeviceOrientations; -import io.sentry.android.core.internal.util.RootChecker; import io.sentry.protocol.App; -import io.sentry.protocol.Device; import io.sentry.protocol.OperatingSystem; import io.sentry.protocol.SentryThread; import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.User; import io.sentry.util.HintUtils; import io.sentry.util.Objects; -import java.io.File; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -54,75 +33,30 @@ final class DefaultAndroidEventProcessor implements EventProcessor { - @TestOnly static final String ROOTED = "rooted"; - @TestOnly static final String KERNEL_VERSION = "kernelVersion"; - @TestOnly static final String EMULATOR = "emulator"; - @TestOnly static final String SIDE_LOADED = "sideLoaded"; - @TestOnly final Context context; - @TestOnly final Future> contextData; - private final @NotNull BuildInfoProvider buildInfoProvider; - private final @NotNull RootChecker rootChecker; private final @NotNull SentryAndroidOptions options; + private final @NotNull Future deviceInfoUtil; public DefaultAndroidEventProcessor( final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider, final @NotNull SentryAndroidOptions options) { - this( - context, - buildInfoProvider, - new RootChecker(context, buildInfoProvider, options.getLogger()), - options); - } - - DefaultAndroidEventProcessor( - final @NotNull Context context, - final @NotNull BuildInfoProvider buildInfoProvider, - final @NotNull RootChecker rootChecker, - final @NotNull SentryAndroidOptions options) { this.context = Objects.requireNonNull(context, "The application context is required."); this.buildInfoProvider = Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required."); - this.rootChecker = Objects.requireNonNull(rootChecker, "The RootChecker is required."); this.options = Objects.requireNonNull(options, "The options object is required."); - ExecutorService executorService = Executors.newSingleThreadExecutor(); // don't ref. to method reference, theres a bug on it - //noinspection Convert2MethodRef - contextData = executorService.submit(() -> loadContextData()); - // reading CPU info performs disk I/O, but it's result is cached, let's pre-cache it - executorService.submit(() -> CpuInfoUtils.getInstance().readMaxFrequencies()); - + // noinspection Convert2MethodRef + // some device info performs disk I/O, but it's result is cached, let's pre-cache it + final @NotNull ExecutorService executorService = Executors.newSingleThreadExecutor(); + this.deviceInfoUtil = + executorService.submit(() -> DeviceInfoUtil.getInstance(context, options)); executorService.shutdown(); } - private @NotNull Map loadContextData() { - Map map = new HashMap<>(); - - if (options.isEnableRootCheck()) { - map.put(ROOTED, rootChecker.isDeviceRooted()); - } - - final String kernelVersion = ContextUtils.getKernelVersion(options.getLogger()); - if (kernelVersion != null) { - map.put(KERNEL_VERSION, kernelVersion); - } - - // its not IO, but it has been cached in the old version as well - map.put(EMULATOR, buildInfoProvider.isEmulator()); - - final Map sideLoadedInfo = - ContextUtils.getSideLoadedInfo(context, options.getLogger(), buildInfoProvider); - if (sideLoadedInfo != null) { - map.put(SIDE_LOADED, sideLoadedInfo); - } - - return map; - } - @Override public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { final boolean applyScopeData = shouldApplyScopeData(event, hint); @@ -145,7 +79,6 @@ private void setCommons( final boolean applyScopeData) { mergeUser(event); setDevice(event, errorEvent, applyScopeData); - mergeOS(event); setSideLoadedInfo(event); } @@ -168,9 +101,9 @@ private void mergeUser(final @NotNull SentryBaseEvent event) { // userId should be set even if event is Cached as the userId is static and won't change anyway. final User user = event.getUser(); if (user == null) { - event.setUser(getDefaultUser()); + event.setUser(getDefaultUser(context)); } else if (user.getId() == null) { - user.setId(getDeviceId()); + user.setId(Installation.id(context)); } } @@ -179,16 +112,26 @@ private void setDevice( final boolean errorEvent, final boolean applyScopeData) { if (event.getContexts().getDevice() == null) { - event.getContexts().setDevice(getDevice(errorEvent, applyScopeData)); + try { + event + .getContexts() + .setDevice(deviceInfoUtil.get().collectDeviceInformation(errorEvent, applyScopeData)); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e); + } + mergeOS(event); } } private void mergeOS(final @NotNull SentryBaseEvent event) { final OperatingSystem currentOS = event.getContexts().getOperatingSystem(); - final OperatingSystem androidOS = getOperatingSystem(); - - // make Android OS the main OS using the 'os' key - event.getContexts().setOperatingSystem(androidOS); + try { + final OperatingSystem androidOS = deviceInfoUtil.get().getOperatingSystem(); + // make Android OS the main OS using the 'os' key + event.getContexts().setOperatingSystem(androidOS); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve os system", e); + } if (currentOS != null) { // add additional OS which was already part of the SentryEvent (eg Linux read from NDK) @@ -210,9 +153,7 @@ private void processNonCachedEvent( app = new App(); } setAppExtras(app, hint); - setPackageInfo(event, app); - event.getContexts().setApp(app); } @@ -269,436 +210,6 @@ private void setAppExtras(final @NotNull App app, final @NotNull Hint hint) { } } - @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) - private @NotNull Long getMemorySize(final @NotNull ActivityManager.MemoryInfo memInfo) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN) { - return memInfo.totalMem; - } - // using Runtime as a fallback - return java.lang.Runtime.getRuntime().totalMemory(); // JVM in bytes too - } - - // we can get some inspiration here - // https://github.com/flutter/plugins/blob/master/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java - private @NotNull Device getDevice(final boolean errorEvent, final boolean applyScopeData) { - // TODO: missing usable memory - - Device device = new Device(); - if (options.isSendDefaultPii()) { - device.setName(ContextUtils.getDeviceName(context, buildInfoProvider)); - } - device.setManufacturer(Build.MANUFACTURER); - device.setBrand(Build.BRAND); - device.setFamily(ContextUtils.getFamily(options.getLogger())); - device.setModel(Build.MODEL); - device.setModelId(Build.ID); - device.setArchs(ContextUtils.getArchitectures(buildInfoProvider)); - - // setting such values require IO hence we don't run for transactions - if (errorEvent) { - if (options.isCollectAdditionalContext()) { - setDeviceIO(device, applyScopeData); - } - } - - device.setOrientation(getOrientation()); - - try { - Object emulator = contextData.get().get(EMULATOR); - if (emulator != null) { - device.setSimulator((Boolean) emulator); - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting emulator.", e); - } - - DisplayMetrics displayMetrics = ContextUtils.getDisplayMetrics(context, options.getLogger()); - if (displayMetrics != null) { - device.setScreenWidthPixels(displayMetrics.widthPixels); - device.setScreenHeightPixels(displayMetrics.heightPixels); - device.setScreenDensity(displayMetrics.density); - device.setScreenDpi(displayMetrics.densityDpi); - } - - device.setBootTime(getBootTime()); - device.setTimezone(getTimeZone()); - - if (device.getId() == null) { - device.setId(getDeviceId()); - } - - final Locale locale = Locale.getDefault(); - if (device.getLanguage() == null) { - device.setLanguage(locale.getLanguage()); - } - if (device.getLocale() == null) { - device.setLocale(locale.toString()); // eg en_US - } - - final @NotNull List cpuFrequencies = CpuInfoUtils.getInstance().readMaxFrequencies(); - if (!cpuFrequencies.isEmpty()) { - device.setProcessorFrequency(Collections.max(cpuFrequencies).doubleValue()); - device.setProcessorCount(cpuFrequencies.size()); - } - - return device; - } - - private void setDeviceIO(final @NotNull Device device, final boolean applyScopeData) { - final Intent batteryIntent = getBatteryIntent(); - if (batteryIntent != null) { - device.setBatteryLevel(getBatteryLevel(batteryIntent)); - device.setCharging(isCharging(batteryIntent)); - device.setBatteryTemperature(getBatteryTemperature(batteryIntent)); - } - - Boolean connected; - switch (ConnectivityChecker.getConnectionStatus(context, options.getLogger())) { - case NOT_CONNECTED: - connected = false; - break; - case CONNECTED: - connected = true; - break; - default: - connected = null; - } - device.setOnline(connected); - - final ActivityManager.MemoryInfo memInfo = - ContextUtils.getMemInfo(context, options.getLogger()); - if (memInfo != null) { - // in bytes - device.setMemorySize(getMemorySize(memInfo)); - if (applyScopeData) { - device.setFreeMemory(memInfo.availMem); - device.setLowMemory(memInfo.lowMemory); - } - // there are runtime.totalMemory() and runtime.freeMemory(), but I kept the same for - // compatibility - } - - // this way of getting the size of storage might be problematic for storages bigger than 2GB - // check the use of - // https://developer.android.com/reference/java/io/File.html#getFreeSpace%28%29 - final File internalStorageFile = context.getExternalFilesDir(null); - if (internalStorageFile != null) { - StatFs internalStorageStat = new StatFs(internalStorageFile.getPath()); - device.setStorageSize(getTotalInternalStorage(internalStorageStat)); - device.setFreeStorage(getUnusedInternalStorage(internalStorageStat)); - } - - final StatFs externalStorageStat = getExternalStorageStat(internalStorageFile); - if (externalStorageStat != null) { - device.setExternalStorageSize(getTotalExternalStorage(externalStorageStat)); - device.setExternalFreeStorage(getUnusedExternalStorage(externalStorageStat)); - } - - if (device.getConnectionType() == null) { - // wifi, ethernet or cellular, null if none - device.setConnectionType( - ConnectivityChecker.getConnectionType(context, options.getLogger(), buildInfoProvider)); - } - } - - @SuppressWarnings("NewApi") - private TimeZone getTimeZone() { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.N) { - LocaleList locales = context.getResources().getConfiguration().getLocales(); - if (!locales.isEmpty()) { - Locale locale = locales.get(0); - return Calendar.getInstance(locale).getTimeZone(); - } - } - return Calendar.getInstance().getTimeZone(); - } - - @SuppressWarnings("JdkObsolete") - private @Nullable Date getBootTime() { - try { - // if user changes the clock, will give a wrong answer, consider ACTION_TIME_CHANGED. - // currentTimeMillis returns UTC already - return DateUtils.getDateTime(System.currentTimeMillis() - SystemClock.elapsedRealtime()); - } catch (IllegalArgumentException e) { - options.getLogger().log(SentryLevel.ERROR, e, "Error getting the device's boot time."); - } - return null; - } - - private @Nullable Intent getBatteryIntent() { - return context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - } - - /** - * Get the device's current battery level (as a percentage of total). - * - * @return the device's current battery level (as a percentage of total), or null if unknown - */ - private @Nullable Float getBatteryLevel(final @NotNull Intent batteryIntent) { - try { - int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - - if (level == -1 || scale == -1) { - return null; - } - - float percentMultiplier = 100.0f; - - return ((float) level / (float) scale) * percentMultiplier; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting device battery level.", e); - return null; - } - } - - /** - * Checks whether or not the device is currently plugged in and charging, or null if unknown. - * - * @return whether or not the device is currently plugged in and charging, or null if unknown - */ - private @Nullable Boolean isCharging(final @NotNull Intent batteryIntent) { - try { - int plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting device charging state.", e); - return null; - } - } - - private @Nullable Float getBatteryTemperature(final @NotNull Intent batteryIntent) { - try { - int temperature = batteryIntent.getIntExtra(EXTRA_TEMPERATURE, -1); - if (temperature != -1) { - return ((float) temperature) / 10; // celsius - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting battery temperature.", e); - } - return null; - } - - /** - * Get the device's current screen orientation. - * - * @return the device's current screen orientation, or null if unknown - */ - @SuppressWarnings("deprecation") - private @Nullable Device.DeviceOrientation getOrientation() { - Device.DeviceOrientation deviceOrientation = null; - try { - deviceOrientation = - DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation); - if (deviceOrientation == null) { - options - .getLogger() - .log( - SentryLevel.INFO, - "No device orientation available (ORIENTATION_SQUARE|ORIENTATION_UNDEFINED)"); - return null; - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting device orientation.", e); - } - return deviceOrientation; - } - - /** - * Get the total amount of internal storage, in bytes. - * - * @return the total amount of internal storage, in bytes - */ - private @Nullable Long getTotalInternalStorage(final @NotNull StatFs stat) { - try { - long blockSize = getBlockSizeLong(stat); - long totalBlocks = getBlockCountLong(stat); - return totalBlocks * blockSize; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting total internal storage amount.", e); - return null; - } - } - - @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) - private long getBlockSizeLong(final @NotNull StatFs stat) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - return stat.getBlockSizeLong(); - } - return getBlockSizeDep(stat); - } - - @SuppressWarnings("deprecation") - private int getBlockSizeDep(final @NotNull StatFs stat) { - return stat.getBlockSize(); - } - - @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) - private long getBlockCountLong(final @NotNull StatFs stat) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - return stat.getBlockCountLong(); - } - return getBlockCountDep(stat); - } - - @SuppressWarnings("deprecation") - private int getBlockCountDep(final @NotNull StatFs stat) { - return stat.getBlockCount(); - } - - @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) - private long getAvailableBlocksLong(final @NotNull StatFs stat) { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - return stat.getAvailableBlocksLong(); - } - return getAvailableBlocksDep(stat); - } - - @SuppressWarnings("deprecation") - private int getAvailableBlocksDep(final @NotNull StatFs stat) { - return stat.getAvailableBlocks(); - } - - /** - * Get the unused amount of internal storage, in bytes. - * - * @return the unused amount of internal storage, in bytes - */ - private @Nullable Long getUnusedInternalStorage(final @NotNull StatFs stat) { - try { - long blockSize = getBlockSizeLong(stat); - long availableBlocks = getAvailableBlocksLong(stat); - return availableBlocks * blockSize; - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Error getting unused internal storage amount.", e); - return null; - } - } - - private @Nullable StatFs getExternalStorageStat(final @Nullable File internalStorage) { - if (!isExternalStorageMounted()) { - File path = getExternalStorageDep(internalStorage); - if (path != null) { // && path.canRead()) { canRead() will read return false - return new StatFs(path.getPath()); - } - options.getLogger().log(SentryLevel.INFO, "Not possible to read external files directory"); - return null; - } - options.getLogger().log(SentryLevel.INFO, "External storage is not mounted or emulated."); - return null; - } - - @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) - private @Nullable File[] getExternalFilesDirs() { - if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.KITKAT) { - return context.getExternalFilesDirs(null); - } else { - File single = context.getExternalFilesDir(null); - if (single != null) { - return new File[] {single}; - } - } - return null; - } - - private @Nullable File getExternalStorageDep(final @Nullable File internalStorage) { - File[] externalFilesDirs = getExternalFilesDirs(); - - if (externalFilesDirs != null) { - // return the 1st file which is not the emulated internal storage - String internalStoragePath = - internalStorage != null ? internalStorage.getAbsolutePath() : null; - for (File file : externalFilesDirs) { - // externalFilesDirs may contain null values :( - if (file == null) { - continue; - } - - // return the 1st file if you cannot compare with the internal one - if (internalStoragePath == null || internalStoragePath.isEmpty()) { - return file; - } - // if we are looking to the same directory, let's check the next one or no external storage - if (file.getAbsolutePath().contains(internalStoragePath)) { - continue; - } - return file; - } - } else { - options.getLogger().log(SentryLevel.INFO, "Not possible to read getExternalFilesDirs"); - } - return null; - } - - /** - * Get the total amount of external storage, in bytes, or null if no external storage is mounted. - * - * @return the total amount of external storage, in bytes, or null if no external storage is - * mounted - */ - private @Nullable Long getTotalExternalStorage(final @NotNull StatFs stat) { - try { - long blockSize = getBlockSizeLong(stat); - long totalBlocks = getBlockCountLong(stat); - return totalBlocks * blockSize; - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting total external storage amount.", e); - return null; - } - } - - private boolean isExternalStorageMounted() { - final String storageState = Environment.getExternalStorageState(); - return (Environment.MEDIA_MOUNTED.equals(storageState) - || Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) - && !Environment.isExternalStorageEmulated(); - } - - /** - * Get the unused amount of external storage, in bytes, or null if no external storage is mounted. - * - * @return the unused amount of external storage, in bytes, or null if no external storage is - * mounted - */ - private @Nullable Long getUnusedExternalStorage(final @NotNull StatFs stat) { - try { - long blockSize = getBlockSizeLong(stat); - long availableBlocks = getAvailableBlocksLong(stat); - return availableBlocks * blockSize; - } catch (Throwable e) { - options - .getLogger() - .log(SentryLevel.ERROR, "Error getting unused external storage amount.", e); - return null; - } - } - - private @NotNull OperatingSystem getOperatingSystem() { - OperatingSystem os = new OperatingSystem(); - os.setName("Android"); - os.setVersion(Build.VERSION.RELEASE); - os.setBuild(Build.DISPLAY); - - try { - Object kernelVersion = contextData.get().get(KERNEL_VERSION); - if (kernelVersion != null) { - os.setKernelVersion((String) kernelVersion); - } - - Object rooted = contextData.get().get(ROOTED); - if (rooted != null) { - os.setRooted((Boolean) rooted); - } - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting OperatingSystem.", e); - } - - return os; - } - @SuppressLint("NewApi") // we perform an if-check for that, but lint fails to recognize private void setAppPackageInfo(final @NotNull App app, final @NotNull PackageInfo packageInfo) { app.setAppIdentifier(packageInfo.packageName); @@ -733,30 +244,18 @@ private void setAppPackageInfo(final @NotNull App app, final @NotNull PackageInf * * @return the User object */ - public @NotNull User getDefaultUser() { - User user = new User(); - user.setId(getDeviceId()); - + public @NotNull User getDefaultUser(final @NotNull Context context) { + final @NotNull User user = new User(); + user.setId(Installation.id(context)); return user; } - private @Nullable String getDeviceId() { - try { - return Installation.id(context); - } catch (Throwable e) { - options.getLogger().log(SentryLevel.ERROR, "Error getting installationId.", e); - } - return null; - } - - @SuppressWarnings("unchecked") private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) { try { - final Object sideLoadedInfo = contextData.get().get(SIDE_LOADED); - - if (sideLoadedInfo instanceof Map) { - for (final Map.Entry entry : - ((Map) sideLoadedInfo).entrySet()) { + final ContextUtils.SideLoadedInfo sideLoadedInfo = deviceInfoUtil.get().getSideLoadedInfo(); + if (sideLoadedInfo != null) { + final @NotNull Map tags = sideLoadedInfo.asTags(); + for (Map.Entry entry : tags.entrySet()) { event.setTag(entry.getKey(), entry.getValue()); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java new file mode 100644 index 00000000000..f9f9d40e791 --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java @@ -0,0 +1,519 @@ +package io.sentry.android.core; + +import static android.os.BatteryManager.EXTRA_TEMPERATURE; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Build; +import android.os.Environment; +import android.os.LocaleList; +import android.os.StatFs; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import io.sentry.DateUtils; +import io.sentry.SentryLevel; +import io.sentry.android.core.internal.util.ConnectivityChecker; +import io.sentry.android.core.internal.util.CpuInfoUtils; +import io.sentry.android.core.internal.util.DeviceOrientations; +import io.sentry.android.core.internal.util.RootChecker; +import io.sentry.protocol.Device; +import io.sentry.protocol.OperatingSystem; +import java.io.File; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +@ApiStatus.Internal +public class DeviceInfoUtil { + + @SuppressLint("StaticFieldLeak") + private static volatile DeviceInfoUtil instance; + + private final @NotNull Context context; + private final @NotNull SentryAndroidOptions options; + private final @NotNull BuildInfoProvider buildInfoProvider; + private final @Nullable Boolean isEmulator; + private final @Nullable ContextUtils.SideLoadedInfo sideLoadedInfo; + private final @NotNull OperatingSystem os; + + public DeviceInfoUtil( + final @NotNull Context context, final @NotNull SentryAndroidOptions options) { + this.context = context; + this.options = options; + this.buildInfoProvider = new BuildInfoProvider(options.getLogger()); + + // these are potentially expense IO operations + CpuInfoUtils.getInstance().readMaxFrequencies(); + os = retrieveOperatingSystemInformation(); + isEmulator = buildInfoProvider.isEmulator(); + sideLoadedInfo = + ContextUtils.retrieveSideLoadedInfo(context, options.getLogger(), buildInfoProvider); + } + + public static @NotNull DeviceInfoUtil getInstance( + final @NotNull Context context, final @NotNull SentryAndroidOptions options) { + if (instance == null) { + synchronized (DeviceInfoUtil.class) { + if (instance == null) { + instance = new DeviceInfoUtil(context.getApplicationContext(), options); + } + } + } + return instance; + } + + @TestOnly + public static void resetInstance() { + instance = null; + } + + // we can get some inspiration here + // https://github.com/flutter/plugins/blob/master/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java + @NotNull + public Device collectDeviceInformation( + final boolean collectDeviceIO, final boolean collectDynamicData) { + // TODO: missing usable memory + final @NotNull Device device = new Device(); + + if (options.isSendDefaultPii()) { + device.setName(ContextUtils.getDeviceName(context, buildInfoProvider)); + } + device.setManufacturer(Build.MANUFACTURER); + device.setBrand(Build.BRAND); + device.setFamily(ContextUtils.getFamily(options.getLogger())); + device.setModel(Build.MODEL); + device.setModelId(Build.ID); + device.setArchs(ContextUtils.getArchitectures(buildInfoProvider)); + + device.setOrientation(getOrientation()); + if (isEmulator != null) { + device.setSimulator(isEmulator); + } + + final @Nullable DisplayMetrics displayMetrics = + ContextUtils.getDisplayMetrics(context, options.getLogger()); + if (displayMetrics != null) { + device.setScreenWidthPixels(displayMetrics.widthPixels); + device.setScreenHeightPixels(displayMetrics.heightPixels); + device.setScreenDensity(displayMetrics.density); + device.setScreenDpi(displayMetrics.densityDpi); + } + + device.setBootTime(getBootTime()); + device.setTimezone(getTimeZone()); + + if (device.getId() == null) { + device.setId(getDeviceId()); + } + + final @NotNull Locale locale = Locale.getDefault(); + if (device.getLanguage() == null) { + device.setLanguage(locale.getLanguage()); + } + if (device.getLocale() == null) { + device.setLocale(locale.toString()); // eg en_US + } + + final @NotNull List cpuFrequencies = CpuInfoUtils.getInstance().readMaxFrequencies(); + if (!cpuFrequencies.isEmpty()) { + device.setProcessorFrequency(Collections.max(cpuFrequencies).doubleValue()); + device.setProcessorCount(cpuFrequencies.size()); + } + + // setting such values require IO hence we don't run for transactions + if (collectDeviceIO && options.isCollectAdditionalContext()) { + setDeviceIO(device, collectDynamicData); + } + + return device; + } + + public final @NotNull OperatingSystem getOperatingSystem() { + return os; + } + + @NotNull + protected OperatingSystem retrieveOperatingSystemInformation() { + + final OperatingSystem os = new OperatingSystem(); + os.setName("Android"); + os.setVersion(Build.VERSION.RELEASE); + os.setBuild(Build.DISPLAY); + + final @Nullable String kernelVersion = ContextUtils.getKernelVersion(options.getLogger()); + if (kernelVersion != null) { + os.setKernelVersion(kernelVersion); + } + + if (options.isEnableRootCheck()) { + final boolean rooted = + new RootChecker(context, buildInfoProvider, options.getLogger()).isDeviceRooted(); + os.setRooted(rooted); + } + return os; + } + + public @Nullable ContextUtils.SideLoadedInfo getSideLoadedInfo() { + return sideLoadedInfo; + } + + private void setDeviceIO(final @NotNull Device device, final boolean includeDynamicData) { + final Intent batteryIntent = getBatteryIntent(); + if (batteryIntent != null) { + device.setBatteryLevel(getBatteryLevel(batteryIntent)); + device.setCharging(isCharging(batteryIntent)); + device.setBatteryTemperature(getBatteryTemperature(batteryIntent)); + } + + Boolean connected; + switch (ConnectivityChecker.getConnectionStatus(context, options.getLogger())) { + case NOT_CONNECTED: + connected = false; + break; + case CONNECTED: + connected = true; + break; + default: + connected = null; + } + device.setOnline(connected); + + final @Nullable ActivityManager.MemoryInfo memInfo = + ContextUtils.getMemInfo(context, options.getLogger()); + if (memInfo != null) { + // in bytes + device.setMemorySize(getMemorySize(memInfo)); + if (includeDynamicData) { + device.setFreeMemory(memInfo.availMem); + device.setLowMemory(memInfo.lowMemory); + } + // there are runtime.totalMemory() and runtime.freeMemory(), but I kept the same for + // compatibility + } + + // this way of getting the size of storage might be problematic for storages bigger than 2GB + // check the use of + // https://developer.android.com/reference/java/io/File.html#getFreeSpace%28%29 + final @Nullable File internalStorageFile = context.getExternalFilesDir(null); + if (internalStorageFile != null) { + StatFs internalStorageStat = new StatFs(internalStorageFile.getPath()); + device.setStorageSize(getTotalInternalStorage(internalStorageStat)); + device.setFreeStorage(getUnusedInternalStorage(internalStorageStat)); + } + + final @Nullable StatFs externalStorageStat = getExternalStorageStat(internalStorageFile); + if (externalStorageStat != null) { + device.setExternalStorageSize(getTotalExternalStorage(externalStorageStat)); + device.setExternalFreeStorage(getUnusedExternalStorage(externalStorageStat)); + } + + if (device.getConnectionType() == null) { + // wifi, ethernet or cellular, null if none + device.setConnectionType( + ConnectivityChecker.getConnectionType(context, options.getLogger(), buildInfoProvider)); + } + } + + @SuppressWarnings("NewApi") + private TimeZone getTimeZone() { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.N) { + LocaleList locales = context.getResources().getConfiguration().getLocales(); + if (!locales.isEmpty()) { + Locale locale = locales.get(0); + return Calendar.getInstance(locale).getTimeZone(); + } + } + return Calendar.getInstance().getTimeZone(); + } + + @SuppressWarnings("JdkObsolete") + private @Nullable Date getBootTime() { + try { + // if user changes the clock, will give a wrong answer, consider ACTION_TIME_CHANGED. + // currentTimeMillis returns UTC already + return DateUtils.getDateTime(System.currentTimeMillis() - SystemClock.elapsedRealtime()); + } catch (IllegalArgumentException e) { + options.getLogger().log(SentryLevel.ERROR, e, "Error getting the device's boot time."); + } + return null; + } + + private @Nullable Intent getBatteryIntent() { + return context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + } + + /** + * Get the device's current battery level (as a percentage of total). + * + * @return the device's current battery level (as a percentage of total), or null if unknown + */ + private @Nullable Float getBatteryLevel(final @NotNull Intent batteryIntent) { + try { + int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + + if (level == -1 || scale == -1) { + return null; + } + + float percentMultiplier = 100.0f; + + return ((float) level / (float) scale) * percentMultiplier; + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting device battery level.", e); + return null; + } + } + + /** + * Checks whether or not the device is currently plugged in and charging, or null if unknown. + * + * @return whether or not the device is currently plugged in and charging, or null if unknown + */ + private @Nullable Boolean isCharging(final @NotNull Intent batteryIntent) { + try { + int plugged = batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting device charging state.", e); + return null; + } + } + + private @Nullable Float getBatteryTemperature(final @NotNull Intent batteryIntent) { + try { + int temperature = batteryIntent.getIntExtra(EXTRA_TEMPERATURE, -1); + if (temperature != -1) { + return ((float) temperature) / 10; // celsius + } + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting battery temperature.", e); + } + return null; + } + + /** + * Get the device's current screen orientation. + * + * @return the device's current screen orientation, or null if unknown + */ + private @Nullable Device.DeviceOrientation getOrientation() { + Device.DeviceOrientation deviceOrientation = null; + try { + deviceOrientation = + DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation); + if (deviceOrientation == null) { + options + .getLogger() + .log( + SentryLevel.INFO, + "No device orientation available (ORIENTATION_SQUARE|ORIENTATION_UNDEFINED)"); + return null; + } + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting device orientation.", e); + } + return deviceOrientation; + } + + @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) + private @NotNull Long getMemorySize(final @NotNull ActivityManager.MemoryInfo memInfo) { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN) { + return memInfo.totalMem; + } + // using Runtime as a fallback + return java.lang.Runtime.getRuntime().totalMemory(); // JVM in bytes too + } + + /** + * Get the total amount of internal storage, in bytes. + * + * @return the total amount of internal storage, in bytes + */ + private @Nullable Long getTotalInternalStorage(final @NotNull StatFs stat) { + try { + long blockSize = getBlockSizeLong(stat); + long totalBlocks = getBlockCountLong(stat); + return totalBlocks * blockSize; + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting total internal storage amount.", e); + return null; + } + } + + @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) + private long getBlockSizeLong(final @NotNull StatFs stat) { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return stat.getBlockSizeLong(); + } + return getBlockSizeDep(stat); + } + + @SuppressWarnings({"deprecation"}) + private int getBlockSizeDep(final @NotNull StatFs stat) { + return stat.getBlockSize(); + } + + @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) + private long getBlockCountLong(final @NotNull StatFs stat) { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return stat.getBlockCountLong(); + } + return getBlockCountDep(stat); + } + + @SuppressWarnings({"deprecation"}) + private int getBlockCountDep(final @NotNull StatFs stat) { + return stat.getBlockCount(); + } + + @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) + private long getAvailableBlocksLong(final @NotNull StatFs stat) { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return stat.getAvailableBlocksLong(); + } + return getAvailableBlocksDep(stat); + } + + @SuppressWarnings({"deprecation"}) + private int getAvailableBlocksDep(final @NotNull StatFs stat) { + return stat.getAvailableBlocks(); + } + + /** + * Get the unused amount of internal storage, in bytes. + * + * @return the unused amount of internal storage, in bytes + */ + private @Nullable Long getUnusedInternalStorage(final @NotNull StatFs stat) { + try { + long blockSize = getBlockSizeLong(stat); + long availableBlocks = getAvailableBlocksLong(stat); + return availableBlocks * blockSize; + } catch (Throwable e) { + options + .getLogger() + .log(SentryLevel.ERROR, "Error getting unused internal storage amount.", e); + return null; + } + } + + private @Nullable StatFs getExternalStorageStat(final @Nullable File internalStorage) { + if (!isExternalStorageMounted()) { + File path = getExternalStorageDep(internalStorage); + if (path != null) { // && path.canRead()) { canRead() will read return false + return new StatFs(path.getPath()); + } + options.getLogger().log(SentryLevel.INFO, "Not possible to read external files directory"); + return null; + } + options.getLogger().log(SentryLevel.INFO, "External storage is not mounted or emulated."); + return null; + } + + @SuppressWarnings({"ObsoleteSdkInt", "NewApi"}) + private @Nullable File[] getExternalFilesDirs() { + if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.KITKAT) { + return context.getExternalFilesDirs(null); + } else { + File single = context.getExternalFilesDir(null); + if (single != null) { + return new File[] {single}; + } + } + return null; + } + + private @Nullable File getExternalStorageDep(final @Nullable File internalStorage) { + final @Nullable File[] externalFilesDirs = getExternalFilesDirs(); + + if (externalFilesDirs != null) { + // return the 1st file which is not the emulated internal storage + String internalStoragePath = + internalStorage != null ? internalStorage.getAbsolutePath() : null; + for (File file : externalFilesDirs) { + // externalFilesDirs may contain null values :( + if (file == null) { + continue; + } + + // return the 1st file if you cannot compare with the internal one + if (internalStoragePath == null || internalStoragePath.isEmpty()) { + return file; + } + // if we are looking to the same directory, let's check the next one or no external storage + if (file.getAbsolutePath().contains(internalStoragePath)) { + continue; + } + return file; + } + } else { + options.getLogger().log(SentryLevel.INFO, "Not possible to read getExternalFilesDirs"); + } + return null; + } + + /** + * Get the total amount of external storage, in bytes, or null if no external storage is mounted. + * + * @return the total amount of external storage, in bytes, or null if no external storage is + * mounted + */ + private @Nullable Long getTotalExternalStorage(final @NotNull StatFs stat) { + try { + final long blockSize = getBlockSizeLong(stat); + final long totalBlocks = getBlockCountLong(stat); + return totalBlocks * blockSize; + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting total external storage amount.", e); + return null; + } + } + + private boolean isExternalStorageMounted() { + final String storageState = Environment.getExternalStorageState(); + return (Environment.MEDIA_MOUNTED.equals(storageState) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) + && !Environment.isExternalStorageEmulated(); + } + + /** + * Get the unused amount of external storage, in bytes, or null if no external storage is mounted. + * + * @return the unused amount of external storage, in bytes, or null if no external storage is + * mounted + */ + private @Nullable Long getUnusedExternalStorage(final @NotNull StatFs stat) { + try { + final long blockSize = getBlockSizeLong(stat); + final long availableBlocks = getAvailableBlocksLong(stat); + return availableBlocks * blockSize; + } catch (Throwable e) { + options + .getLogger() + .log(SentryLevel.ERROR, "Error getting unused external storage amount.", e); + return null; + } + } + + private @Nullable String getDeviceId() { + try { + return Installation.id(context); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error getting installationId.", e); + } + return null; + } +} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java index 007bb306cdd..4ff7a661fa1 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java @@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -final class Installation { +public final class Installation { @TestOnly static @Nullable String deviceId = null; @TestOnly static final String INSTALLATION = "INSTALLATION"; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java new file mode 100644 index 00000000000..bb0277d68de --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -0,0 +1,73 @@ +package io.sentry.android.core; + +import android.content.Context; +import io.sentry.HubAdapter; +import io.sentry.ILogger; +import io.sentry.ObjectWriter; +import io.sentry.Scope; +import io.sentry.SentryLevel; +import io.sentry.protocol.Device; +import io.sentry.protocol.User; +import io.sentry.util.MapObjectWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public final class InternalSentrySdk { + + /** + * @return a copy of the current hub's topmost scope, or null in case the hub is disabled + */ + @Nullable + public static Scope getCurrentScope() { + final @NotNull AtomicReference scopeRef = new AtomicReference<>(); + HubAdapter.getInstance().withScope(scopeRef::set); + return scopeRef.get(); + } + + @NotNull + public static Map serializeScope( + @NotNull Context context, @NotNull SentryAndroidOptions options, @Nullable Scope scope) { + final @NotNull Map data = new HashMap<>(); + if (scope == null) { + return data; + } + + final @NotNull ILogger logger = options.getLogger(); + final @NotNull ObjectWriter writer = new MapObjectWriter(data); + + final @NotNull DeviceInfoUtil deviceInfoUtil = DeviceInfoUtil.getInstance(context, options); + final @NotNull Device deviceInfo = deviceInfoUtil.collectDeviceInformation(false, false); + scope.getContexts().setDevice(deviceInfo); + scope.getContexts().setOperatingSystem(deviceInfoUtil.getOperatingSystem()); + + @Nullable User user = scope.getUser(); + if (user == null) { + user = new User(); + user.setId(Installation.id(context)); + } + if (user.getId() == null) { + user.setId(Installation.id(context)); + } + scope.setUser(user); + + try { + writer.name("user").value(logger, scope.getUser()); + writer.name("contexts").value(logger, scope.getContexts()); + writer.name("tags").value(logger, scope.getTags()); + writer.name("extras").value(logger, scope.getExtras()); + writer.name("fingerprint").value(logger, scope.getFingerprint()); + writer.name("level").value(logger, scope.getLevel()); + writer.name("breadcrumbs").value(logger, scope.getBreadcrumbs()); + } catch (Exception e) { + options.getLogger().log(SentryLevel.ERROR, "Could not serialize scope.", e); + return new HashMap<>(); + } + + return data; + } +} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsUnitTests.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsUnitTests.kt index 8f8846b9547..cdb3463297f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsUnitTests.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsUnitTests.kt @@ -19,6 +19,7 @@ import org.robolectric.shadows.ShadowBuild import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -83,8 +84,8 @@ class ContextUtilsUnitTests { @Test fun `isSideLoaded returns true for test context`() { val sideLoadedInfo = - ContextUtils.getSideLoadedInfo(context, logger, BuildInfoProvider(logger)) - assertEquals("true", sideLoadedInfo?.get("isSideLoaded")) + ContextUtils.retrieveSideLoadedInfo(context, logger, BuildInfoProvider(logger)) + assertTrue(sideLoadedInfo!!.isSideLoaded) } @Test @@ -96,9 +97,9 @@ class ContextUtilsUnitTests { whenever(mock.packageManager).thenReturn(mockedPackageManager) } val sideLoadedInfo = - ContextUtils.getSideLoadedInfo(mockedContext, logger, BuildInfoProvider(logger)) - assertEquals("false", sideLoadedInfo?.get("isSideLoaded")) - assertEquals("play.google.com", sideLoadedInfo?.get("installerStore")) + ContextUtils.retrieveSideLoadedInfo(mockedContext, logger, BuildInfoProvider(logger)) + assertFalse(sideLoadedInfo!!.isSideLoaded) + assertEquals("play.google.com", sideLoadedInfo.installerStore) } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt index 8747409ccea..514826ff967 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt @@ -13,10 +13,6 @@ import io.sentry.SentryLevel import io.sentry.SentryTracer import io.sentry.TransactionContext import io.sentry.TypeCheckHint.SENTRY_DART_SDK_NAME -import io.sentry.android.core.DefaultAndroidEventProcessor.EMULATOR -import io.sentry.android.core.DefaultAndroidEventProcessor.KERNEL_VERSION -import io.sentry.android.core.DefaultAndroidEventProcessor.ROOTED -import io.sentry.android.core.DefaultAndroidEventProcessor.SIDE_LOADED import io.sentry.android.core.internal.util.CpuInfoUtils import io.sentry.protocol.OperatingSystem import io.sentry.protocol.SdkVersion @@ -80,6 +76,7 @@ class DefaultAndroidEventProcessorTest { fun `set up`() { context = ApplicationProvider.getApplicationContext() AppState.getInstance().resetInstance() + DeviceInfoUtil.resetInstance() } @Test @@ -295,19 +292,6 @@ class DefaultAndroidEventProcessorTest { } } - @Test - fun `Executor service should be called on ctor`() { - val sut = fixture.getSut(context) - - val contextData = sut.contextData.get() - - assertNotNull(contextData) - assertNotNull(contextData[ROOTED]) - assertNotNull(contextData[KERNEL_VERSION]) - assertNotNull(contextData[EMULATOR]) - assertNotNull(contextData[SIDE_LOADED]) - } - @Test fun `Processor won't throw exception`() { val sut = fixture.getSut(context) @@ -323,7 +307,7 @@ class DefaultAndroidEventProcessorTest { @Test fun `Processor won't throw exception when theres a hint`() { val processor = - DefaultAndroidEventProcessor(context, fixture.buildInfo, mock(), fixture.options) + DefaultAndroidEventProcessor(context, fixture.buildInfo, fixture.options) val hints = HintUtils.createWithTypeCheckHint(CachedEvent()) processor.process(SentryEvent(), hints) @@ -576,13 +560,4 @@ class DefaultAndroidEventProcessorTest { assertNull(thread.isMain) } } - - @Test - fun `does not perform root check if root checker is disabled`() { - fixture.options.isEnableRootCheck = false - val sut = fixture.getSut(context) - - val contextData = sut.contextData.get() - assertNull(contextData[ROOTED]) - } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/DeviceInfoUtilTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/DeviceInfoUtilTest.kt new file mode 100644 index 00000000000..5c90395e3b8 --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/DeviceInfoUtilTest.kt @@ -0,0 +1,156 @@ +package io.sentry.android.core + +import android.content.Context +import android.content.Intent +import android.os.BatteryManager +import android.provider.Settings +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.android.core.internal.util.CpuInfoUtils +import org.junit.runner.RunWith +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +class DeviceInfoUtilTest { + + private lateinit var context: Context + + @Suppress("deprecation") + @BeforeTest + fun `set up`() { + context = ApplicationProvider.getApplicationContext() + context.sendStickyBroadcast( + Intent(Intent.ACTION_BATTERY_CHANGED).putExtra( + BatteryManager.EXTRA_LEVEL, + 75 + ).putExtra(BatteryManager.EXTRA_PLUGGED, 0) + ) + Settings.Global.putString(context.contentResolver, "device_name", "sentry") + DeviceInfoUtil.resetInstance() + } + + @Test + fun `provides os and sideloaded info`() { + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, SentryAndroidOptions()) + + val os = deviceInfoUtil.operatingSystem + val sideLoadedInfo = deviceInfoUtil.sideLoadedInfo + val deviceInfo = deviceInfoUtil.collectDeviceInformation(false, false) + + assertNotNull(os.kernelVersion) + assertNotNull(os.isRooted) + + assertNotNull(sideLoadedInfo) + assertNotNull(sideLoadedInfo.isSideLoaded) + + assertNotNull(deviceInfo.isSimulator) + } + + @Test + fun `does not include device name when PII is disabled`() { + val deviceInfoUtil = DeviceInfoUtil.getInstance( + context, + SentryAndroidOptions().apply { + isSendDefaultPii = false + } + ) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(false, false) + assertNull(deviceInfo.name) + } + + @Test + fun `does include device name when pii is enabled`() { + val deviceInfoUtil = DeviceInfoUtil.getInstance( + context, + SentryAndroidOptions().apply { + isSendDefaultPii = true + } + ) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(false, false) + assertNotNull(deviceInfo.name) + } + + @Test + fun `does include cpu data`() { + CpuInfoUtils.getInstance().setCpuMaxFrequencies(listOf(1024)) + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, SentryAndroidOptions()) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(false, false) + + assertEquals(1, deviceInfo.processorCount) + assertEquals(1024.0, deviceInfo.processorFrequency) + } + + @Test + fun `does include device io data when enabled`() { + val options = SentryAndroidOptions().apply { + isCollectAdditionalContext = true + } + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, options) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(true, false) + + // all values are 0 when running via robolectric + assertNotNull(deviceInfo.memorySize) + assertNotNull(deviceInfo.storageSize) + assertNotNull(deviceInfo.freeStorage) + } + + @Test + fun `does not include device io data when disabled`() { + val options = SentryAndroidOptions().apply { + isCollectAdditionalContext = true + } + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, options) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(false, false) + + assertNull(deviceInfo.memorySize) + assertNull(deviceInfo.storageSize) + assertNull(deviceInfo.freeStorage) + } + + @Test + fun `does include dynamic data when enabled`() { + val options = SentryAndroidOptions().apply { + isCollectAdditionalContext = true + } + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, options) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(true, true) + + // all values are 0 when running via robolectric + assertNotNull(deviceInfo.freeMemory) + assertNotNull(deviceInfo.isLowMemory) + } + + @Test + fun `does not include dynamic data when disabled`() { + val options = SentryAndroidOptions().apply { + isCollectAdditionalContext = true + } + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, options) + val deviceInfo = deviceInfoUtil.collectDeviceInformation(true, false) + + assertNull(deviceInfo.freeMemory) + assertNull(deviceInfo.isLowMemory) + } + + @Test + fun `does perform root check if root checker is enabled`() { + val options = SentryAndroidOptions().apply { + isEnableRootCheck = true + } + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, options) + assertNotNull(deviceInfoUtil.operatingSystem.isRooted) + } + + @Test + fun `does not perform root check if root checker is disabled`() { + val options = SentryAndroidOptions().apply { + isEnableRootCheck = false + } + val deviceInfoUtil = DeviceInfoUtil.getInstance(context, options) + assertNull(deviceInfoUtil.operatingSystem.isRooted) + } +} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt new file mode 100644 index 00000000000..f8e112d46d4 --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt @@ -0,0 +1,106 @@ +package io.sentry.android.core + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.Breadcrumb +import io.sentry.Hub +import io.sentry.NoOpHub +import io.sentry.Scope +import io.sentry.Sentry +import io.sentry.SentryOptions +import io.sentry.protocol.App +import io.sentry.protocol.Contexts +import io.sentry.protocol.User +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class InternalSentrySdkTest { + + private lateinit var context: Context + + @BeforeTest + fun `set up`() { + context = ApplicationProvider.getApplicationContext() + DeviceInfoUtil.resetInstance() + } + + @Test + fun `current scope returns null when hub is no-op`() { + Sentry.setCurrentHub(NoOpHub.getInstance()) + val scope = InternalSentrySdk.getCurrentScope() + assertNull(scope) + } + + @Test + fun `current scope returns obj when hub is active`() { + Sentry.setCurrentHub( + Hub( + SentryOptions().apply { + dsn = "https://key@uri/1234567" + } + ) + ) + val scope = InternalSentrySdk.getCurrentScope() + assertNotNull(scope) + } + + @Test + fun `serializeScope correctly creates top level map`() { + val options = SentryAndroidOptions() + val scope = Scope(options) + + scope.user = User().apply { + name = "John" + } + scope.addBreadcrumb(Breadcrumb.ui("ui.click", "button_login")) + scope.contexts.setApp( + App().apply { + appName = "Example App" + } + ) + scope.setTag("variant", "yellow") + + val serializedScope = InternalSentrySdk.serializeScope( + context, + options, + scope + ) + + assertTrue(serializedScope.containsKey("user")) + assertTrue(serializedScope.containsKey("contexts")) + assertTrue((serializedScope["contexts"] as Map<*, *>).containsKey("device")) + + assertTrue(serializedScope.containsKey("tags")) + assertTrue(serializedScope.containsKey("extras")) + assertTrue(serializedScope.containsKey("fingerprint")) + assertTrue(serializedScope.containsKey("level")) + assertTrue(serializedScope.containsKey("breadcrumbs")) + } + + @Test + fun `serializeScope returns empty map in case scope is null`() { + val options = SentryAndroidOptions() + val serializedScope = InternalSentrySdk.serializeScope(context, options, null) + assertTrue(serializedScope.isEmpty()) + } + + @Test + fun `serializeScope returns empty map in case scope serialization fails`() { + val options = SentryAndroidOptions() + val scope = mock() + + whenever(scope.contexts).thenReturn(Contexts()) + whenever(scope.user).thenThrow(IllegalStateException("something is off")) + + val serializedScope = InternalSentrySdk.serializeScope(context, options, scope) + assertTrue(serializedScope.isEmpty()) + } +} diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index f636d6775ba..5b68f2e844f 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -112,7 +112,7 @@ public final class io/sentry/Breadcrumb : io/sentry/JsonSerializable, io/sentry/ public static fun navigation (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/Breadcrumb; public static fun query (Ljava/lang/String;)Lio/sentry/Breadcrumb; public fun removeData (Ljava/lang/String;)V - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setCategory (Ljava/lang/String;)V public fun setData (Ljava/lang/String;Ljava/lang/Object;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -709,14 +709,38 @@ public final class io/sentry/JsonObjectSerializer { public static final field OBJECT_PLACEHOLDER Ljava/lang/String; public final field jsonReflectionObjectSerializer Lio/sentry/JsonReflectionObjectSerializer; public fun (I)V - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;Ljava/lang/Object;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;Ljava/lang/Object;)V } -public final class io/sentry/JsonObjectWriter : io/sentry/vendor/gson/stream/JsonWriter { +public final class io/sentry/JsonObjectWriter : io/sentry/ObjectWriter { public fun (Ljava/io/Writer;I)V + public fun beginArray ()Lio/sentry/JsonObjectWriter; + public synthetic fun beginArray ()Lio/sentry/ObjectWriter; + public fun beginObject ()Lio/sentry/JsonObjectWriter; + public synthetic fun beginObject ()Lio/sentry/ObjectWriter; + public fun endArray ()Lio/sentry/JsonObjectWriter; + public synthetic fun endArray ()Lio/sentry/ObjectWriter; + public fun endObject ()Lio/sentry/JsonObjectWriter; + public synthetic fun endObject ()Lio/sentry/ObjectWriter; public fun name (Ljava/lang/String;)Lio/sentry/JsonObjectWriter; - public synthetic fun name (Ljava/lang/String;)Lio/sentry/vendor/gson/stream/JsonWriter; + public synthetic fun name (Ljava/lang/String;)Lio/sentry/ObjectWriter; + public fun nullValue ()Lio/sentry/JsonObjectWriter; + public synthetic fun nullValue ()Lio/sentry/ObjectWriter; + public fun setIndent (Ljava/lang/String;)V + public fun value (D)Lio/sentry/JsonObjectWriter; + public synthetic fun value (D)Lio/sentry/ObjectWriter; + public fun value (J)Lio/sentry/JsonObjectWriter; + public synthetic fun value (J)Lio/sentry/ObjectWriter; public fun value (Lio/sentry/ILogger;Ljava/lang/Object;)Lio/sentry/JsonObjectWriter; + public synthetic fun value (Lio/sentry/ILogger;Ljava/lang/Object;)Lio/sentry/ObjectWriter; + public fun value (Ljava/lang/Boolean;)Lio/sentry/JsonObjectWriter; + public synthetic fun value (Ljava/lang/Boolean;)Lio/sentry/ObjectWriter; + public fun value (Ljava/lang/Number;)Lio/sentry/JsonObjectWriter; + public synthetic fun value (Ljava/lang/Number;)Lio/sentry/ObjectWriter; + public fun value (Ljava/lang/String;)Lio/sentry/JsonObjectWriter; + public synthetic fun value (Ljava/lang/String;)Lio/sentry/ObjectWriter; + public fun value (Z)Lio/sentry/JsonObjectWriter; + public synthetic fun value (Z)Lio/sentry/ObjectWriter; } public final class io/sentry/JsonReflectionObjectSerializer { @@ -725,7 +749,7 @@ public final class io/sentry/JsonReflectionObjectSerializer { } public abstract interface class io/sentry/JsonSerializable { - public abstract fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public abstract fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V } public final class io/sentry/JsonSerializer : io/sentry/ISerializer { @@ -974,6 +998,22 @@ public final class io/sentry/NoOpTransportFactory : io/sentry/ITransportFactory public static fun getInstance ()Lio/sentry/NoOpTransportFactory; } +public abstract interface class io/sentry/ObjectWriter { + public abstract fun beginArray ()Lio/sentry/ObjectWriter; + public abstract fun beginObject ()Lio/sentry/ObjectWriter; + public abstract fun endArray ()Lio/sentry/ObjectWriter; + public abstract fun endObject ()Lio/sentry/ObjectWriter; + public abstract fun name (Ljava/lang/String;)Lio/sentry/ObjectWriter; + public abstract fun nullValue ()Lio/sentry/ObjectWriter; + public abstract fun value (D)Lio/sentry/ObjectWriter; + public abstract fun value (J)Lio/sentry/ObjectWriter; + public abstract fun value (Lio/sentry/ILogger;Ljava/lang/Object;)Lio/sentry/ObjectWriter; + public abstract fun value (Ljava/lang/Boolean;)Lio/sentry/ObjectWriter; + public abstract fun value (Ljava/lang/Number;)Lio/sentry/ObjectWriter; + public abstract fun value (Ljava/lang/String;)Lio/sentry/ObjectWriter; + public abstract fun value (Z)Lio/sentry/ObjectWriter; +} + public final class io/sentry/OptionsContainer { public static fun create (Ljava/lang/Class;)Lio/sentry/OptionsContainer; public fun createInstance ()Ljava/lang/Object; @@ -1026,7 +1066,7 @@ public final class io/sentry/ProfilingTraceData : io/sentry/JsonSerializable, io public fun getUnknown ()Ljava/util/Map; public fun isDeviceIsEmulator ()Z public fun readDeviceCpuFrequencies ()V - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setAndroidApiLevel (I)V public fun setBuildId (Ljava/lang/String;)V public fun setCpuArchitecture (Ljava/lang/String;)V @@ -1100,7 +1140,7 @@ public final class io/sentry/ProfilingTransactionData : io/sentry/JsonSerializab public fun getUnknown ()Ljava/util/Map; public fun hashCode ()I public fun notifyFinish (Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;)V - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setId (Ljava/lang/String;)V public fun setName (Ljava/lang/String;)V public fun setRelativeEndNs (Ljava/lang/Long;)V @@ -1168,7 +1208,10 @@ public final class io/sentry/Scope { public fun clearAttachments ()V public fun clearBreadcrumbs ()V public fun clearTransaction ()V + public fun getBreadcrumbs ()Ljava/util/Queue; public fun getContexts ()Lio/sentry/protocol/Contexts; + public fun getExtras ()Ljava/util/Map; + public fun getFingerprint ()Ljava/util/List; public fun getLevel ()Lio/sentry/SentryLevel; public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRequest ()Lio/sentry/protocol/Request; @@ -1390,7 +1433,7 @@ public final class io/sentry/SentryBaseEvent$JsonKeys { public final class io/sentry/SentryBaseEvent$Serializer { public fun ()V - public fun serialize (Lio/sentry/SentryBaseEvent;Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/SentryBaseEvent;Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V } public final class io/sentry/SentryClient : io/sentry/ISentryClient { @@ -1447,7 +1490,7 @@ public final class io/sentry/SentryEnvelopeHeader : io/sentry/JsonSerializable, public fun getSentAt ()Ljava/util/Date; public fun getTraceContext ()Lio/sentry/TraceContext; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setSentAt (Ljava/util/Date;)V public fun setUnknown (Ljava/util/Map;)V } @@ -1488,7 +1531,7 @@ public final class io/sentry/SentryEnvelopeItemHeader : io/sentry/JsonSerializab public fun getLength ()I public fun getType ()Lio/sentry/SentryItemType; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -1524,7 +1567,7 @@ public final class io/sentry/SentryEvent : io/sentry/SentryBaseEvent, io/sentry/ public fun isCrashed ()Z public fun isErrored ()Z public fun removeModule (Ljava/lang/String;)V - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setExceptions (Ljava/util/List;)V public fun setFingerprints (Ljava/util/List;)V public fun setLevel (Lio/sentry/SentryLevel;)V @@ -1596,7 +1639,7 @@ public final class io/sentry/SentryItemType : java/lang/Enum, io/sentry/JsonSeri public static final field UserFeedback Lio/sentry/SentryItemType; public fun getItemType ()Ljava/lang/String; public static fun resolve (Ljava/lang/Object;)Lio/sentry/SentryItemType; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public static fun valueOf (Ljava/lang/String;)Lio/sentry/SentryItemType; public static fun valueOfLabel (Ljava/lang/String;)Lio/sentry/SentryItemType; public static fun values ()[Lio/sentry/SentryItemType; @@ -1608,7 +1651,7 @@ public final class io/sentry/SentryLevel : java/lang/Enum, io/sentry/JsonSeriali public static final field FATAL Lio/sentry/SentryLevel; public static final field INFO Lio/sentry/SentryLevel; public static final field WARNING Lio/sentry/SentryLevel; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public static fun valueOf (Ljava/lang/String;)Lio/sentry/SentryLevel; public static fun values ()[Lio/sentry/SentryLevel; } @@ -1629,7 +1672,7 @@ public final class io/sentry/SentryLockReason : io/sentry/JsonSerializable, io/s public fun getType ()I public fun getUnknown ()Ljava/util/Map; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setAddress (Ljava/lang/String;)V public fun setClassName (Ljava/lang/String;)V public fun setPackageName (Ljava/lang/String;)V @@ -2013,7 +2056,7 @@ public final class io/sentry/Session : io/sentry/JsonSerializable, io/sentry/Jso public fun getTimestamp ()Ljava/util/Date; public fun getUnknown ()Ljava/util/Map; public fun getUserAgent ()Ljava/lang/String; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setInitAsTrue ()V public fun setUnknown (Ljava/util/Map;)V public fun update (Lio/sentry/Session$State;Ljava/lang/String;Z)Z @@ -2128,7 +2171,7 @@ public class io/sentry/SpanContext : io/sentry/JsonSerializable, io/sentry/JsonU public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun getUnknown ()Ljava/util/Map; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setDescription (Ljava/lang/String;)V public fun setOperation (Ljava/lang/String;)V public fun setSampled (Ljava/lang/Boolean;)V @@ -2172,7 +2215,7 @@ public final class io/sentry/SpanId : io/sentry/JsonSerializable { public fun (Ljava/lang/String;)V public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun toString ()Ljava/lang/String; } @@ -2213,7 +2256,7 @@ public final class io/sentry/SpanStatus : java/lang/Enum, io/sentry/JsonSerializ public static final field UNKNOWN_ERROR Lio/sentry/SpanStatus; public static fun fromHttpStatusCode (I)Lio/sentry/SpanStatus; public static fun fromHttpStatusCode (Ljava/lang/Integer;Lio/sentry/SpanStatus;)Lio/sentry/SpanStatus; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public static fun valueOf (Ljava/lang/String;)Lio/sentry/SpanStatus; public static fun values ()[Lio/sentry/SpanStatus; } @@ -2242,7 +2285,7 @@ public final class io/sentry/TraceContext : io/sentry/JsonSerializable, io/sentr public fun getUnknown ()Ljava/util/Map; public fun getUserId ()Ljava/lang/String; public fun getUserSegment ()Ljava/lang/String; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -2384,7 +2427,7 @@ public final class io/sentry/UserFeedback : io/sentry/JsonSerializable, io/sentr public fun getEventId ()Lio/sentry/protocol/SentryId; public fun getName ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setComments (Ljava/lang/String;)V public fun setEmail (Ljava/lang/String;)V public fun setName (Ljava/lang/String;)V @@ -2482,7 +2525,7 @@ public final class io/sentry/clientreport/ClientReport : io/sentry/JsonSerializa public fun getDiscardedEvents ()Ljava/util/List; public fun getTimestamp ()Ljava/util/Date; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -2525,7 +2568,7 @@ public final class io/sentry/clientreport/DiscardedEvent : io/sentry/JsonSeriali public fun getQuantity ()Ljava/lang/Long; public fun getReason ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V public fun toString ()Ljava/lang/String; } @@ -2805,7 +2848,7 @@ public final class io/sentry/profilemeasurements/ProfileMeasurement : io/sentry/ public fun getUnknown ()Ljava/util/Map; public fun getValues ()Ljava/util/Collection; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnit (Ljava/lang/String;)V public fun setUnknown (Ljava/util/Map;)V public fun setValues (Ljava/util/Collection;)V @@ -2831,7 +2874,7 @@ public final class io/sentry/profilemeasurements/ProfileMeasurementValue : io/se public fun getUnknown ()Ljava/util/Map; public fun getValue ()D public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -2862,7 +2905,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr public fun getPermissions ()Ljava/util/Map; public fun getUnknown ()Ljava/util/Map; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setAppBuild (Ljava/lang/String;)V public fun setAppIdentifier (Ljava/lang/String;)V public fun setAppName (Ljava/lang/String;)V @@ -2902,7 +2945,7 @@ public final class io/sentry/protocol/Browser : io/sentry/JsonSerializable, io/s public fun getUnknown ()Ljava/util/Map; public fun getVersion ()Ljava/lang/String; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setName (Ljava/lang/String;)V public fun setUnknown (Ljava/util/Map;)V public fun setVersion (Ljava/lang/String;)V @@ -2931,7 +2974,7 @@ public final class io/sentry/protocol/Contexts : java/util/concurrent/Concurrent public fun getResponse ()Lio/sentry/protocol/Response; public fun getRuntime ()Lio/sentry/protocol/SentryRuntime; public fun getTrace ()Lio/sentry/SpanContext; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setApp (Lio/sentry/protocol/App;)V public fun setBrowser (Lio/sentry/protocol/Browser;)V public fun setDevice (Lio/sentry/protocol/Device;)V @@ -2962,7 +3005,7 @@ public final class io/sentry/protocol/DebugImage : io/sentry/JsonSerializable, i public fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun getUuid ()Ljava/lang/String; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setArch (Ljava/lang/String;)V public fun setCodeFile (Ljava/lang/String;)V public fun setCodeId (Ljava/lang/String;)V @@ -3000,7 +3043,7 @@ public final class io/sentry/protocol/DebugMeta : io/sentry/JsonSerializable, io public fun getImages ()Ljava/util/List; public fun getSdkInfo ()Lio/sentry/protocol/SdkInfo; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setImages (Ljava/util/List;)V public fun setSdkInfo (Lio/sentry/protocol/SdkInfo;)V public fun setUnknown (Ljava/util/Map;)V @@ -3058,7 +3101,7 @@ public final class io/sentry/protocol/Device : io/sentry/JsonSerializable, io/se public fun isLowMemory ()Ljava/lang/Boolean; public fun isOnline ()Ljava/lang/Boolean; public fun isSimulator ()Ljava/lang/Boolean; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setArchs ([Ljava/lang/String;)V public fun setBatteryLevel (Ljava/lang/Float;)V public fun setBatteryTemperature (Ljava/lang/Float;)V @@ -3105,7 +3148,7 @@ public final class io/sentry/protocol/Device$Deserializer : io/sentry/JsonDeseri public final class io/sentry/protocol/Device$DeviceOrientation : java/lang/Enum, io/sentry/JsonSerializable { public static final field LANDSCAPE Lio/sentry/protocol/Device$DeviceOrientation; public static final field PORTRAIT Lio/sentry/protocol/Device$DeviceOrientation; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public static fun valueOf (Ljava/lang/String;)Lio/sentry/protocol/Device$DeviceOrientation; public static fun values ()[Lio/sentry/protocol/Device$DeviceOrientation; } @@ -3162,7 +3205,7 @@ public final class io/sentry/protocol/Geo : io/sentry/JsonSerializable, io/sentr public fun getCountryCode ()Ljava/lang/String; public fun getRegion ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setCity (Ljava/lang/String;)V public fun setCountryCode (Ljava/lang/String;)V public fun setRegion (Ljava/lang/String;)V @@ -3197,7 +3240,7 @@ public final class io/sentry/protocol/Gpu : io/sentry/JsonSerializable, io/sentr public fun getVersion ()Ljava/lang/String; public fun hashCode ()I public fun isMultiThreadedRendering ()Ljava/lang/Boolean; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setApiType (Ljava/lang/String;)V public fun setId (Ljava/lang/Integer;)V public fun setMemorySize (Ljava/lang/Integer;)V @@ -3242,7 +3285,7 @@ public final class io/sentry/protocol/MeasurementValue : io/sentry/JsonSerializa public fun getUnit ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun getValue ()Ljava/lang/Number; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -3269,7 +3312,7 @@ public final class io/sentry/protocol/Mechanism : io/sentry/JsonSerializable, io public fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun isHandled ()Ljava/lang/Boolean; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setData (Ljava/util/Map;)V public fun setDescription (Ljava/lang/String;)V public fun setHandled (Ljava/lang/Boolean;)V @@ -3303,7 +3346,7 @@ public final class io/sentry/protocol/Message : io/sentry/JsonSerializable, io/s public fun getMessage ()Ljava/lang/String; public fun getParams ()Ljava/util/List; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setFormatted (Ljava/lang/String;)V public fun setMessage (Ljava/lang/String;)V public fun setParams (Ljava/util/List;)V @@ -3335,7 +3378,7 @@ public final class io/sentry/protocol/OperatingSystem : io/sentry/JsonSerializab public fun getVersion ()Ljava/lang/String; public fun hashCode ()I public fun isRooted ()Ljava/lang/Boolean; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setBuild (Ljava/lang/String;)V public fun setKernelVersion (Ljava/lang/String;)V public fun setName (Ljava/lang/String;)V @@ -3378,7 +3421,7 @@ public final class io/sentry/protocol/Request : io/sentry/JsonSerializable, io/s public fun getUnknown ()Ljava/util/Map; public fun getUrl ()Ljava/lang/String; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setApiTarget (Ljava/lang/String;)V public fun setBodySize (Ljava/lang/Long;)V public fun setCookies (Ljava/lang/String;)V @@ -3424,7 +3467,7 @@ public final class io/sentry/protocol/Response : io/sentry/JsonSerializable, io/ public fun getHeaders ()Ljava/util/Map; public fun getStatusCode ()Ljava/lang/Integer; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setBodySize (Ljava/lang/Long;)V public fun setCookies (Ljava/lang/String;)V public fun setData (Ljava/lang/Object;)V @@ -3455,7 +3498,7 @@ public final class io/sentry/protocol/SdkInfo : io/sentry/JsonSerializable, io/s public fun getVersionMajor ()Ljava/lang/Integer; public fun getVersionMinor ()Ljava/lang/Integer; public fun getVersionPatchlevel ()Ljava/lang/Integer; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setSdkName (Ljava/lang/String;)V public fun setUnknown (Ljava/util/Map;)V public fun setVersionMajor (Ljava/lang/Integer;)V @@ -3490,7 +3533,7 @@ public final class io/sentry/protocol/SdkVersion : io/sentry/JsonSerializable, i public fun getUnknown ()Ljava/util/Map; public fun getVersion ()Ljava/lang/String; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setName (Ljava/lang/String;)V public fun setUnknown (Ljava/util/Map;)V public fun setVersion (Ljava/lang/String;)V @@ -3520,7 +3563,7 @@ public final class io/sentry/protocol/SentryException : io/sentry/JsonSerializab public fun getType ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun getValue ()Ljava/lang/String; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setMechanism (Lio/sentry/protocol/Mechanism;)V public fun setModule (Ljava/lang/String;)V public fun setStacktrace (Lio/sentry/protocol/SentryStackTrace;)V @@ -3553,7 +3596,7 @@ public final class io/sentry/protocol/SentryId : io/sentry/JsonSerializable { public fun (Ljava/util/UUID;)V public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun toString ()Ljava/lang/String; } @@ -3570,7 +3613,7 @@ public final class io/sentry/protocol/SentryPackage : io/sentry/JsonSerializable public fun getUnknown ()Ljava/util/Map; public fun getVersion ()Ljava/lang/String; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setName (Ljava/lang/String;)V public fun setUnknown (Ljava/util/Map;)V public fun setVersion (Ljava/lang/String;)V @@ -3595,7 +3638,7 @@ public final class io/sentry/protocol/SentryRuntime : io/sentry/JsonSerializable public fun getRawDescription ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun getVersion ()Ljava/lang/String; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setName (Ljava/lang/String;)V public fun setRawDescription (Ljava/lang/String;)V public fun setUnknown (Ljava/util/Map;)V @@ -3631,7 +3674,7 @@ public final class io/sentry/protocol/SentrySpan : io/sentry/JsonSerializable, i public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun getUnknown ()Ljava/util/Map; public fun isFinished ()Z - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -3679,7 +3722,7 @@ public final class io/sentry/protocol/SentryStackFrame : io/sentry/JsonSerializa public fun getVars ()Ljava/util/Map; public fun isInApp ()Ljava/lang/Boolean; public fun isNative ()Ljava/lang/Boolean; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setAbsPath (Ljava/lang/String;)V public fun setColno (Ljava/lang/Integer;)V public fun setContextLine (Ljava/lang/String;)V @@ -3738,7 +3781,7 @@ public final class io/sentry/protocol/SentryStackTrace : io/sentry/JsonSerializa public fun getRegisters ()Ljava/util/Map; public fun getSnapshot ()Ljava/lang/Boolean; public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setFrames (Ljava/util/List;)V public fun setRegisters (Ljava/util/Map;)V public fun setSnapshot (Ljava/lang/Boolean;)V @@ -3771,7 +3814,7 @@ public final class io/sentry/protocol/SentryThread : io/sentry/JsonSerializable, 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 serialize (Lio/sentry/ObjectWriter;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 @@ -3819,7 +3862,7 @@ public final class io/sentry/protocol/SentryTransaction : io/sentry/SentryBaseEv public fun getUnknown ()Ljava/util/Map; public fun isFinished ()Z public fun isSampled ()Z - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -3843,7 +3886,7 @@ public final class io/sentry/protocol/SentryTransaction$JsonKeys { public final class io/sentry/protocol/TransactionInfo : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun (Ljava/lang/String;)V public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -3886,7 +3929,7 @@ public final class io/sentry/protocol/User : io/sentry/JsonSerializable, io/sent public fun getUnknown ()Ljava/util/Map; public fun getUsername ()Ljava/lang/String; public fun hashCode ()I - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setData (Ljava/util/Map;)V public fun setEmail (Ljava/lang/String;)V public fun setGeo (Lio/sentry/protocol/Geo;)V @@ -3923,7 +3966,7 @@ public final class io/sentry/protocol/ViewHierarchy : io/sentry/JsonSerializable public fun getRenderingSystem ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; public fun getWindows ()Ljava/util/List; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setUnknown (Ljava/util/Map;)V } @@ -3953,7 +3996,7 @@ public final class io/sentry/protocol/ViewHierarchyNode : io/sentry/JsonSerializ public fun getWidth ()Ljava/lang/Double; public fun getX ()Ljava/lang/Double; public fun getY ()Ljava/lang/Double; - public fun serialize (Lio/sentry/JsonObjectWriter;Lio/sentry/ILogger;)V + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setAlpha (Ljava/lang/Double;)V public fun setChildren (Ljava/util/List;)V public fun setHeight (Ljava/lang/Double;)V @@ -4165,6 +4208,36 @@ public final class io/sentry/util/LogUtils { public static fun logNotInstanceOf (Ljava/lang/Class;Ljava/lang/Object;Lio/sentry/ILogger;)V } +public final class io/sentry/util/MapObjectWriter : io/sentry/ObjectWriter { + public fun (Ljava/util/Map;)V + public synthetic fun beginArray ()Lio/sentry/ObjectWriter; + public fun beginArray ()Lio/sentry/util/MapObjectWriter; + public synthetic fun beginObject ()Lio/sentry/ObjectWriter; + public fun beginObject ()Lio/sentry/util/MapObjectWriter; + public synthetic fun endArray ()Lio/sentry/ObjectWriter; + public fun endArray ()Lio/sentry/util/MapObjectWriter; + public synthetic fun endObject ()Lio/sentry/ObjectWriter; + public fun endObject ()Lio/sentry/util/MapObjectWriter; + public synthetic fun name (Ljava/lang/String;)Lio/sentry/ObjectWriter; + public fun name (Ljava/lang/String;)Lio/sentry/util/MapObjectWriter; + public synthetic fun nullValue ()Lio/sentry/ObjectWriter; + public fun nullValue ()Lio/sentry/util/MapObjectWriter; + public synthetic fun value (D)Lio/sentry/ObjectWriter; + public fun value (D)Lio/sentry/util/MapObjectWriter; + public synthetic fun value (J)Lio/sentry/ObjectWriter; + public fun value (J)Lio/sentry/util/MapObjectWriter; + public synthetic fun value (Lio/sentry/ILogger;Ljava/lang/Object;)Lio/sentry/ObjectWriter; + public fun value (Lio/sentry/ILogger;Ljava/lang/Object;)Lio/sentry/util/MapObjectWriter; + public synthetic fun value (Ljava/lang/Boolean;)Lio/sentry/ObjectWriter; + public fun value (Ljava/lang/Boolean;)Lio/sentry/util/MapObjectWriter; + public synthetic fun value (Ljava/lang/Number;)Lio/sentry/ObjectWriter; + public fun value (Ljava/lang/Number;)Lio/sentry/util/MapObjectWriter; + public synthetic fun value (Ljava/lang/String;)Lio/sentry/ObjectWriter; + public fun value (Ljava/lang/String;)Lio/sentry/util/MapObjectWriter; + public synthetic fun value (Z)Lio/sentry/ObjectWriter; + public fun value (Z)Lio/sentry/util/MapObjectWriter; +} + public final class io/sentry/util/Objects { public static fun equals (Ljava/lang/Object;Ljava/lang/Object;)Z public static fun hash ([Ljava/lang/Object;)I diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index de48d0d3d78..4085d901a15 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -561,8 +561,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); if (message != null) { diff --git a/sentry/src/main/java/io/sentry/JsonObjectSerializer.java b/sentry/src/main/java/io/sentry/JsonObjectSerializer.java index e51cffc92cc..629b3fc417b 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonObjectSerializer.java @@ -33,7 +33,7 @@ public JsonObjectSerializer(int maxDepth) { } public void serialize( - @NotNull JsonObjectWriter writer, @NotNull ILogger logger, @Nullable Object object) + @NotNull ObjectWriter writer, @NotNull ILogger logger, @Nullable Object object) throws IOException { if (object == null) { writer.nullValue(); @@ -89,7 +89,7 @@ public void serialize( // Helper private void serializeDate( - @NotNull JsonObjectWriter writer, @NotNull ILogger logger, @NotNull Date date) + @NotNull ObjectWriter writer, @NotNull ILogger logger, @NotNull Date date) throws IOException { try { writer.value(DateUtils.getTimestamp(date)); @@ -100,7 +100,7 @@ private void serializeDate( } private void serializeTimeZone( - @NotNull JsonObjectWriter writer, @NotNull ILogger logger, @NotNull TimeZone timeZone) + @NotNull ObjectWriter writer, @NotNull ILogger logger, @NotNull TimeZone timeZone) throws IOException { try { writer.value(timeZone.getID()); @@ -111,7 +111,7 @@ private void serializeTimeZone( } private void serializeCollection( - @NotNull JsonObjectWriter writer, @NotNull ILogger logger, @NotNull Collection collection) + @NotNull ObjectWriter writer, @NotNull ILogger logger, @NotNull Collection collection) throws IOException { writer.beginArray(); for (Object object : collection) { @@ -121,7 +121,7 @@ private void serializeCollection( } private void serializeMap( - @NotNull JsonObjectWriter writer, @NotNull ILogger logger, @NotNull Map map) + @NotNull ObjectWriter writer, @NotNull ILogger logger, @NotNull Map map) throws IOException { writer.beginObject(); for (Object key : map.keySet()) { diff --git a/sentry/src/main/java/io/sentry/JsonObjectWriter.java b/sentry/src/main/java/io/sentry/JsonObjectWriter.java index 08d200b25e8..314a9013aa5 100644 --- a/sentry/src/main/java/io/sentry/JsonObjectWriter.java +++ b/sentry/src/main/java/io/sentry/JsonObjectWriter.java @@ -6,18 +6,85 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class JsonObjectWriter extends JsonWriter { +public final class JsonObjectWriter implements ObjectWriter { + private final JsonWriter jsonWriter; private final JsonObjectSerializer jsonObjectSerializer; public JsonObjectWriter(Writer out, int maxDepth) { - super(out); + jsonWriter = new JsonWriter(out); jsonObjectSerializer = new JsonObjectSerializer(maxDepth); } + @Override + public JsonObjectWriter beginArray() throws IOException { + jsonWriter.beginArray(); + return this; + } + + @Override + public JsonObjectWriter endArray() throws IOException { + jsonWriter.endArray(); + return this; + } + + @Override + public JsonObjectWriter beginObject() throws IOException { + jsonWriter.beginObject(); + return this; + } + + @Override + public JsonObjectWriter endObject() throws IOException { + jsonWriter.endObject(); + return this; + } + @Override public JsonObjectWriter name(String name) throws IOException { - super.name(name); + jsonWriter.name(name); + return this; + } + + @Override + public JsonObjectWriter value(String value) throws IOException { + jsonWriter.value(value); + return this; + } + + @Override + public JsonObjectWriter nullValue() throws IOException { + jsonWriter.nullValue(); + return this; + } + + @Override + public JsonObjectWriter value(boolean value) throws IOException { + jsonWriter.value(value); + return this; + } + + @Override + public JsonObjectWriter value(Boolean value) throws IOException { + jsonWriter.value(value); + return this; + } + + @Override + public JsonObjectWriter value(double value) throws IOException { + jsonWriter.value(value); + return this; + } + + @Override + public JsonObjectWriter value(long value) throws IOException { + jsonWriter.value(value); + return this; + } + + @Override + public JsonObjectWriter value(Number value) throws IOException { + jsonWriter.value(value); return this; } @@ -29,9 +96,14 @@ public JsonObjectWriter name(String name) throws IOException { * @param object Object to encode. May be null. * @return this writer. */ + @Override public JsonObjectWriter value(@NotNull ILogger logger, @Nullable Object object) throws IOException { jsonObjectSerializer.serialize(this, logger, object); return this; } + + public void setIndent(@NotNull final String indent) { + jsonWriter.setIndent(indent); + } } diff --git a/sentry/src/main/java/io/sentry/JsonSerializable.java b/sentry/src/main/java/io/sentry/JsonSerializable.java index f3ae2cc605e..7cbc093927b 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializable.java +++ b/sentry/src/main/java/io/sentry/JsonSerializable.java @@ -4,5 +4,5 @@ import org.jetbrains.annotations.NotNull; public interface JsonSerializable { - void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) throws IOException; + void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException; } diff --git a/sentry/src/main/java/io/sentry/ObjectWriter.java b/sentry/src/main/java/io/sentry/ObjectWriter.java new file mode 100644 index 00000000000..bab6e507de8 --- /dev/null +++ b/sentry/src/main/java/io/sentry/ObjectWriter.java @@ -0,0 +1,33 @@ +package io.sentry; + +import java.io.IOException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ObjectWriter { + ObjectWriter beginArray() throws IOException; + + ObjectWriter endArray() throws IOException; + + ObjectWriter beginObject() throws IOException; + + ObjectWriter endObject() throws IOException; + + ObjectWriter name(String name) throws IOException; + + ObjectWriter value(String value) throws IOException; + + ObjectWriter nullValue() throws IOException; + + ObjectWriter value(boolean value) throws IOException; + + ObjectWriter value(Boolean value) throws IOException; + + ObjectWriter value(double value) throws IOException; + + ObjectWriter value(long value) throws IOException; + + ObjectWriter value(Number value) throws IOException; + + ObjectWriter value(@NotNull ILogger logger, @Nullable Object object) throws IOException; +} diff --git a/sentry/src/main/java/io/sentry/ProfilingTraceData.java b/sentry/src/main/java/io/sentry/ProfilingTraceData.java index 09582a6f4fe..0502a07d737 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTraceData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTraceData.java @@ -385,7 +385,7 @@ public static final class JsonKeys { } @Override - public void serialize(final @NotNull JsonObjectWriter writer, final @NotNull ILogger logger) + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.ANDROID_API_LEVEL).value(logger, androidApiLevel); diff --git a/sentry/src/main/java/io/sentry/ProfilingTransactionData.java b/sentry/src/main/java/io/sentry/ProfilingTransactionData.java index 3591fdd8286..7717ae481aa 100644 --- a/sentry/src/main/java/io/sentry/ProfilingTransactionData.java +++ b/sentry/src/main/java/io/sentry/ProfilingTransactionData.java @@ -144,8 +144,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.ID).value(logger, id); writer.name(JsonKeys.TRACE_ID).value(logger, traceId); diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index 9e659849136..01e57e6e9b2 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -285,8 +285,9 @@ public void setRequest(final @Nullable Request request) { * * @return the fingerprint list */ + @ApiStatus.Internal @NotNull - List getFingerprint() { + public List getFingerprint() { return fingerprint; } @@ -311,8 +312,9 @@ public void setFingerprint(final @NotNull List fingerprint) { * * @return the breadcrumbs queue */ + @ApiStatus.Internal @NotNull - Queue getBreadcrumbs() { + public Queue getBreadcrumbs() { return breadcrumbs; } @@ -477,8 +479,9 @@ public void removeTag(final @NotNull String key) { * * @return the extra map */ + @ApiStatus.Internal @NotNull - Map getExtras() { + public Map getExtras() { return extra; } @@ -802,6 +805,11 @@ public void withTransaction(final @NotNull IWithTransaction callback) { } } + @NotNull + SentryOptions getOptions() { + return options; + } + @ApiStatus.Internal public @Nullable Session getSession() { return session; diff --git a/sentry/src/main/java/io/sentry/SentryBaseEvent.java b/sentry/src/main/java/io/sentry/SentryBaseEvent.java index ed0593f9a51..7e7babe8197 100644 --- a/sentry/src/main/java/io/sentry/SentryBaseEvent.java +++ b/sentry/src/main/java/io/sentry/SentryBaseEvent.java @@ -345,9 +345,7 @@ public static final class JsonKeys { public static final class Serializer { public void serialize( - @NotNull SentryBaseEvent baseEvent, - @NotNull JsonObjectWriter writer, - @NotNull ILogger logger) + @NotNull SentryBaseEvent baseEvent, @NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { if (baseEvent.eventId != null) { writer.name(JsonKeys.EVENT_ID).value(logger, baseEvent.eventId); diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java b/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java index b788a6f1a5a..1c29aea2443 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeHeader.java @@ -89,8 +89,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (eventId != null) { writer.name(JsonKeys.EVENT_ID).value(logger, eventId); diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java index 285edf37e51..25ed2172e7e 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItemHeader.java @@ -103,8 +103,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (contentType != null) { writer.name(JsonKeys.CONTENT_TYPE).value(contentType); diff --git a/sentry/src/main/java/io/sentry/SentryEvent.java b/sentry/src/main/java/io/sentry/SentryEvent.java index 8eeea440282..2462f60593d 100644 --- a/sentry/src/main/java/io/sentry/SentryEvent.java +++ b/sentry/src/main/java/io/sentry/SentryEvent.java @@ -248,8 +248,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); if (message != null) { diff --git a/sentry/src/main/java/io/sentry/SentryItemType.java b/sentry/src/main/java/io/sentry/SentryItemType.java index 04f62de3399..a097573ebf2 100644 --- a/sentry/src/main/java/io/sentry/SentryItemType.java +++ b/sentry/src/main/java/io/sentry/SentryItemType.java @@ -54,8 +54,7 @@ public String getItemType() { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(itemType); } diff --git a/sentry/src/main/java/io/sentry/SentryLevel.java b/sentry/src/main/java/io/sentry/SentryLevel.java index 065cc44cc52..cdaa72fc970 100644 --- a/sentry/src/main/java/io/sentry/SentryLevel.java +++ b/sentry/src/main/java/io/sentry/SentryLevel.java @@ -13,8 +13,7 @@ public enum SentryLevel implements JsonSerializable { FATAL; @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(name().toLowerCase(Locale.ROOT)); } diff --git a/sentry/src/main/java/io/sentry/SentryLockReason.java b/sentry/src/main/java/io/sentry/SentryLockReason.java index 607e461ee96..2c59176bd96 100644 --- a/sentry/src/main/java/io/sentry/SentryLockReason.java +++ b/sentry/src/main/java/io/sentry/SentryLockReason.java @@ -117,8 +117,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.TYPE).value(type); if (address != null) { diff --git a/sentry/src/main/java/io/sentry/Session.java b/sentry/src/main/java/io/sentry/Session.java index cfe437912c4..9f78aed53a3 100644 --- a/sentry/src/main/java/io/sentry/Session.java +++ b/sentry/src/main/java/io/sentry/Session.java @@ -357,8 +357,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (sessionId != null) { writer.name(JsonKeys.SID).value(sessionId.toString()); diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index 7b387a8fb28..81ffb826c54 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -231,8 +231,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.TRACE_ID); traceId.serialize(writer, logger); diff --git a/sentry/src/main/java/io/sentry/SpanId.java b/sentry/src/main/java/io/sentry/SpanId.java index 8764578887b..01aa62162e7 100644 --- a/sentry/src/main/java/io/sentry/SpanId.java +++ b/sentry/src/main/java/io/sentry/SpanId.java @@ -44,8 +44,7 @@ public String toString() { // JsonElementSerializer @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(value); } diff --git a/sentry/src/main/java/io/sentry/SpanStatus.java b/sentry/src/main/java/io/sentry/SpanStatus.java index d8a4377ee65..3fe24494d57 100644 --- a/sentry/src/main/java/io/sentry/SpanStatus.java +++ b/sentry/src/main/java/io/sentry/SpanStatus.java @@ -106,8 +106,7 @@ private boolean matches(int httpStatusCode) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(name().toLowerCase(Locale.ROOT)); } diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index 10564e9208c..88bf08905b8 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -193,8 +193,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(TraceContext.JsonKeys.TRACE_ID).value(logger, traceId); writer.name(TraceContext.JsonKeys.PUBLIC_KEY).value(publicKey); diff --git a/sentry/src/main/java/io/sentry/UserFeedback.java b/sentry/src/main/java/io/sentry/UserFeedback.java index 5b7740e2048..71ea6e857ff 100644 --- a/sentry/src/main/java/io/sentry/UserFeedback.java +++ b/sentry/src/main/java/io/sentry/UserFeedback.java @@ -147,8 +147,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.EVENT_ID); eventId.serialize(writer, logger); diff --git a/sentry/src/main/java/io/sentry/clientreport/ClientReport.java b/sentry/src/main/java/io/sentry/clientreport/ClientReport.java index 0f58486e978..a9703bb183b 100644 --- a/sentry/src/main/java/io/sentry/clientreport/ClientReport.java +++ b/sentry/src/main/java/io/sentry/clientreport/ClientReport.java @@ -4,9 +4,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -55,8 +55,7 @@ public void setUnknown(@Nullable Map unknown) { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.TIMESTAMP).value(DateUtils.getTimestamp(timestamp)); diff --git a/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java b/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java index 350843c1536..de5cc12a1c0 100644 --- a/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java +++ b/sentry/src/main/java/io/sentry/clientreport/DiscardedEvent.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -72,8 +72,7 @@ public void setUnknown(@Nullable Map unknown) { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.REASON).value(reason); diff --git a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java index 3d5f2240d56..76a2ee18bd4 100644 --- a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java +++ b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurement.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -72,7 +72,7 @@ public static final class JsonKeys { } @Override - public void serialize(final @NotNull JsonObjectWriter writer, final @NotNull ILogger logger) + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.UNIT).value(logger, unit); diff --git a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java index 0e5e83aac8c..9639ba892ff 100644 --- a/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java +++ b/sentry/src/main/java/io/sentry/profilemeasurements/ProfileMeasurementValue.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -62,7 +62,7 @@ public static final class JsonKeys { } @Override - public void serialize(final @NotNull JsonObjectWriter writer, final @NotNull ILogger logger) + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.VALUE).value(logger, value); diff --git a/sentry/src/main/java/io/sentry/protocol/App.java b/sentry/src/main/java/io/sentry/protocol/App.java index 4ace02b52e6..c221a60df28 100644 --- a/sentry/src/main/java/io/sentry/protocol/App.java +++ b/sentry/src/main/java/io/sentry/protocol/App.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -184,8 +184,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (appIdentifier != null) { writer.name(JsonKeys.APP_IDENTIFIER).value(appIdentifier); diff --git a/sentry/src/main/java/io/sentry/protocol/Browser.java b/sentry/src/main/java/io/sentry/protocol/Browser.java index 260ef1ec64a..136c9be2c9e 100644 --- a/sentry/src/main/java/io/sentry/protocol/Browser.java +++ b/sentry/src/main/java/io/sentry/protocol/Browser.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -81,8 +81,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (name != null) { writer.name(JsonKeys.NAME).value(name); diff --git a/sentry/src/main/java/io/sentry/protocol/Contexts.java b/sentry/src/main/java/io/sentry/protocol/Contexts.java index fe72ad0a376..ce0c3219d0c 100644 --- a/sentry/src/main/java/io/sentry/protocol/Contexts.java +++ b/sentry/src/main/java/io/sentry/protocol/Contexts.java @@ -3,8 +3,8 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; +import io.sentry.ObjectWriter; import io.sentry.SpanContext; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -122,7 +122,7 @@ public void setResponse(final @NotNull Response response) { // region json @Override - public void serialize(final @NotNull JsonObjectWriter writer, final @NotNull ILogger logger) + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); // Serialize in alphabetical order to keep determinism. diff --git a/sentry/src/main/java/io/sentry/protocol/DebugImage.java b/sentry/src/main/java/io/sentry/protocol/DebugImage.java index 20d81143dbb..00321900f15 100644 --- a/sentry/src/main/java/io/sentry/protocol/DebugImage.java +++ b/sentry/src/main/java/io/sentry/protocol/DebugImage.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.HashMap; @@ -271,8 +271,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (uuid != null) { writer.name(JsonKeys.UUID).value(uuid); diff --git a/sentry/src/main/java/io/sentry/protocol/DebugMeta.java b/sentry/src/main/java/io/sentry/protocol/DebugMeta.java index af582cf3dae..b06a9a72439 100644 --- a/sentry/src/main/java/io/sentry/protocol/DebugMeta.java +++ b/sentry/src/main/java/io/sentry/protocol/DebugMeta.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.ArrayList; @@ -73,8 +73,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (sdkInfo != null) { writer.name(JsonKeys.SDK_INFO).value(logger, sdkInfo); diff --git a/sentry/src/main/java/io/sentry/protocol/Device.java b/sentry/src/main/java/io/sentry/protocol/Device.java index 46f12618883..88897f1d78e 100644 --- a/sentry/src/main/java/io/sentry/protocol/Device.java +++ b/sentry/src/main/java/io/sentry/protocol/Device.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -534,7 +534,7 @@ public enum DeviceOrientation implements JsonSerializable { // JsonElementSerializer @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(toString().toLowerCase(Locale.ROOT)); } @@ -590,8 +590,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (name != null) { writer.name(JsonKeys.NAME).value(name); diff --git a/sentry/src/main/java/io/sentry/protocol/Geo.java b/sentry/src/main/java/io/sentry/protocol/Geo.java index a4c85fb8241..90fd86954e5 100644 --- a/sentry/src/main/java/io/sentry/protocol/Geo.java +++ b/sentry/src/main/java/io/sentry/protocol/Geo.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.Map; @@ -136,7 +136,7 @@ public void setUnknown(@Nullable Map unknown) { } @Override - public void serialize(JsonObjectWriter writer, ILogger logger) throws IOException { + public void serialize(@NotNull ObjectWriter writer, ILogger logger) throws IOException { writer.beginObject(); if (city != null) { writer.name(JsonKeys.CITY).value(city); diff --git a/sentry/src/main/java/io/sentry/protocol/Gpu.java b/sentry/src/main/java/io/sentry/protocol/Gpu.java index b8a5a1e3bc2..e2a2a83577b 100644 --- a/sentry/src/main/java/io/sentry/protocol/Gpu.java +++ b/sentry/src/main/java/io/sentry/protocol/Gpu.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -176,8 +176,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (name != null) { writer.name(JsonKeys.NAME).value(name); diff --git a/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java b/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java index 60539f83e70..0aec89acc35 100644 --- a/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java +++ b/sentry/src/main/java/io/sentry/protocol/MeasurementValue.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -78,8 +78,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.VALUE).value(value); diff --git a/sentry/src/main/java/io/sentry/protocol/Mechanism.java b/sentry/src/main/java/io/sentry/protocol/Mechanism.java index e2002de1697..b72d2d7a54f 100644 --- a/sentry/src/main/java/io/sentry/protocol/Mechanism.java +++ b/sentry/src/main/java/io/sentry/protocol/Mechanism.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -167,8 +167,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (type != null) { writer.name(JsonKeys.TYPE).value(type); diff --git a/sentry/src/main/java/io/sentry/protocol/Message.java b/sentry/src/main/java/io/sentry/protocol/Message.java index ad9b49764a9..9ebd0ebeb84 100644 --- a/sentry/src/main/java/io/sentry/protocol/Message.java +++ b/sentry/src/main/java/io/sentry/protocol/Message.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -94,8 +94,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (formatted != null) { writer.name(JsonKeys.FORMATTED).value(formatted); diff --git a/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java b/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java index 6a8107aaa0e..e4eaea2608f 100644 --- a/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java +++ b/sentry/src/main/java/io/sentry/protocol/OperatingSystem.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -134,8 +134,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (name != null) { writer.name(JsonKeys.NAME).value(name); diff --git a/sentry/src/main/java/io/sentry/protocol/Request.java b/sentry/src/main/java/io/sentry/protocol/Request.java index 8d5e0cda4b6..1a069937153 100644 --- a/sentry/src/main/java/io/sentry/protocol/Request.java +++ b/sentry/src/main/java/io/sentry/protocol/Request.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -277,8 +277,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (url != null) { writer.name(JsonKeys.URL).value(url); diff --git a/sentry/src/main/java/io/sentry/protocol/Response.java b/sentry/src/main/java/io/sentry/protocol/Response.java index dcfec08f46a..23a16c78f8c 100644 --- a/sentry/src/main/java/io/sentry/protocol/Response.java +++ b/sentry/src/main/java/io/sentry/protocol/Response.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -121,7 +121,7 @@ public static final class JsonKeys { } @Override - public void serialize(final @NotNull JsonObjectWriter writer, final @NotNull ILogger logger) + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); diff --git a/sentry/src/main/java/io/sentry/protocol/SdkInfo.java b/sentry/src/main/java/io/sentry/protocol/SdkInfo.java index cb5ff593b76..49fe2df0798 100644 --- a/sentry/src/main/java/io/sentry/protocol/SdkInfo.java +++ b/sentry/src/main/java/io/sentry/protocol/SdkInfo.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.HashMap; @@ -88,8 +88,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (sdkName != null) { writer.name(JsonKeys.SDK_NAME).value(sdkName); diff --git a/sentry/src/main/java/io/sentry/protocol/SdkVersion.java b/sentry/src/main/java/io/sentry/protocol/SdkVersion.java index eaa0aa34c91..1122301e1ce 100644 --- a/sentry/src/main/java/io/sentry/protocol/SdkVersion.java +++ b/sentry/src/main/java/io/sentry/protocol/SdkVersion.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SentryLevel; import io.sentry.util.Objects; @@ -197,8 +197,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.NAME).value(name); writer.name(JsonKeys.VERSION).value(version); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryException.java b/sentry/src/main/java/io/sentry/protocol/SentryException.java index 88ba2149e87..28973334a81 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryException.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryException.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.HashMap; @@ -187,8 +187,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (type != null) { writer.name(JsonKeys.TYPE).value(type); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryId.java b/sentry/src/main/java/io/sentry/protocol/SentryId.java index 3e4450536f6..c68a72ac064 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryId.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryId.java @@ -3,8 +3,8 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; +import io.sentry.ObjectWriter; import io.sentry.util.StringUtils; import java.io.IOException; import java.util.UUID; @@ -73,8 +73,7 @@ public int hashCode() { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.value(toString()); } diff --git a/sentry/src/main/java/io/sentry/protocol/SentryPackage.java b/sentry/src/main/java/io/sentry/protocol/SentryPackage.java index a8ed584bd09..351ad171916 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryPackage.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryPackage.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -82,8 +82,7 @@ public void setUnknown(@Nullable Map unknown) { // JsonSerializable @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.NAME).value(name); writer.name(JsonKeys.VERSION).value(version); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java b/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java index f8ceaf411ce..5b751a718c3 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryRuntime.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -75,8 +75,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (name != null) { writer.name(JsonKeys.NAME).value(name); diff --git a/sentry/src/main/java/io/sentry/protocol/SentrySpan.java b/sentry/src/main/java/io/sentry/protocol/SentrySpan.java index e733cf7cac9..4e73c424954 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentrySpan.java +++ b/sentry/src/main/java/io/sentry/protocol/SentrySpan.java @@ -4,9 +4,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.Span; import io.sentry.SpanId; @@ -149,8 +149,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); writer.name(JsonKeys.START_TIMESTAMP).value(logger, doubleToBigDecimal(startTimestamp)); if (timestamp != null) { diff --git a/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java b/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java index f11e91fd75c..a8186a8fb9f 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryStackFrame.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLockReason; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -331,8 +331,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (filename != null) { writer.name(JsonKeys.FILENAME).value(filename); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java b/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java index d4d222dba82..1bdb1c9d321 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryStackTrace.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.util.CollectionUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -128,8 +128,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (frames != null) { writer.name(JsonKeys.FRAMES).value(logger, frames); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryThread.java b/sentry/src/main/java/io/sentry/protocol/SentryThread.java index b3e1c7c6a66..3f33284cd8f 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryThread.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryThread.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLockReason; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -257,8 +257,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (id != null) { writer.name(JsonKeys.ID).value(id); diff --git a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java index 34f40ade040..3d8beaa459f 100644 --- a/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java +++ b/sentry/src/main/java/io/sentry/protocol/SentryTransaction.java @@ -4,9 +4,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryBaseEvent; import io.sentry.SentryTracer; import io.sentry.Span; @@ -181,8 +181,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (transaction != null) { writer.name(JsonKeys.TRANSACTION).value(transaction); diff --git a/sentry/src/main/java/io/sentry/protocol/TransactionInfo.java b/sentry/src/main/java/io/sentry/protocol/TransactionInfo.java index 976008e53c9..4e2f81f7c3f 100644 --- a/sentry/src/main/java/io/sentry/protocol/TransactionInfo.java +++ b/sentry/src/main/java/io/sentry/protocol/TransactionInfo.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.Map; @@ -31,8 +31,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (source != null) { writer.name(JsonKeys.SOURCE).value(logger, source); diff --git a/sentry/src/main/java/io/sentry/protocol/User.java b/sentry/src/main/java/io/sentry/protocol/User.java index a6ff46b4d0c..5620f0a1e66 100644 --- a/sentry/src/main/java/io/sentry/protocol/User.java +++ b/sentry/src/main/java/io/sentry/protocol/User.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.util.CollectionUtils; @@ -377,8 +377,7 @@ public static final class JsonKeys { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (email != null) { writer.name(JsonKeys.EMAIL).value(email); diff --git a/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java b/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java index 9a24ccc8352..b83748df386 100644 --- a/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java +++ b/sentry/src/main/java/io/sentry/protocol/ViewHierarchy.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.HashMap; @@ -42,8 +42,7 @@ public List getWindows() { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (renderingSystem != null) { writer.name(JsonKeys.RENDERING_SYSTEM).value(renderingSystem); diff --git a/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java b/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java index d751b27a801..e2b7f4fe52f 100644 --- a/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java +++ b/sentry/src/main/java/io/sentry/protocol/ViewHierarchyNode.java @@ -3,9 +3,9 @@ import io.sentry.ILogger; import io.sentry.JsonDeserializer; import io.sentry.JsonObjectReader; -import io.sentry.JsonObjectWriter; import io.sentry.JsonSerializable; import io.sentry.JsonUnknown; +import io.sentry.ObjectWriter; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.HashMap; @@ -145,8 +145,7 @@ public List getChildren() { } @Override - public void serialize(@NotNull JsonObjectWriter writer, @NotNull ILogger logger) - throws IOException { + public void serialize(@NotNull ObjectWriter writer, @NotNull ILogger logger) throws IOException { writer.beginObject(); if (renderingSystem != null) { writer.name(JsonKeys.RENDERING_SYSTEM).value(renderingSystem); diff --git a/sentry/src/main/java/io/sentry/util/MapObjectWriter.java b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java new file mode 100644 index 00000000000..9417841ea3d --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/MapObjectWriter.java @@ -0,0 +1,236 @@ +package io.sentry.util; + +import static io.sentry.util.JsonSerializationUtils.atomicIntegerArrayToList; +import static io.sentry.util.JsonSerializationUtils.calendarToMap; + +import io.sentry.DateUtils; +import io.sentry.ILogger; +import io.sentry.JsonSerializable; +import io.sentry.ObjectWriter; +import io.sentry.SentryLevel; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Currency; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicIntegerArray; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class MapObjectWriter implements ObjectWriter { + + final @NotNull Map root; + /** + * The stack for maintaining the hierarchy and structure Possible elements: Map<>: An Object + * List<>: Array of objects String: The key for the object on top + */ + final @NotNull ArrayDeque stack; + + public MapObjectWriter(final @NotNull Map root) { + this.root = root; + stack = new ArrayDeque<>(); + stack.addLast(root); + } + + @Override + public MapObjectWriter name(final @NotNull String name) throws IOException { + stack.add(name); + return this; + } + + @Override + public MapObjectWriter value(final @NotNull ILogger logger, final @Nullable Object object) + throws IOException { + if (object == null) { + nullValue(); + } else if (object instanceof Character) { + value(Character.toString((Character) object)); + } else if (object instanceof String) { + value((String) object); + } else if (object instanceof Boolean) { + value((boolean) object); + } else if (object instanceof Number) { + value((Number) object); + } else if (object instanceof Date) { + serializeDate(logger, (Date) object); + } else if (object instanceof TimeZone) { + serializeTimeZone(logger, (TimeZone) object); + } else if (object instanceof JsonSerializable) { + ((JsonSerializable) object).serialize(this, logger); + } else if (object instanceof Collection) { + serializeCollection(logger, (Collection) object); + } else if (object.getClass().isArray()) { + serializeCollection(logger, Arrays.asList((Object[]) object)); + } else if (object instanceof Map) { + serializeMap(logger, (Map) object); + } else if (object instanceof Locale) { + value(object.toString()); + } else if (object instanceof AtomicIntegerArray) { + serializeCollection(logger, atomicIntegerArrayToList((AtomicIntegerArray) object)); + } else if (object instanceof AtomicBoolean) { + value(((AtomicBoolean) object).get()); + } else if (object instanceof URI) { + value(object.toString()); + } else if (object instanceof InetAddress) { + value(object.toString()); + } else if (object instanceof UUID) { + value(object.toString()); + } else if (object instanceof Currency) { + value(object.toString()); + } else if (object instanceof Calendar) { + serializeMap(logger, calendarToMap((Calendar) object)); + } else if (object.getClass().isEnum()) { + value(object.toString()); + } else { + logger.log(SentryLevel.WARNING, "Failed serializing unknown object.", object); + } + return this; + } + + @Override + public MapObjectWriter beginArray() throws IOException { + stack.add(new ArrayList<>()); + return this; + } + + @Override + public MapObjectWriter endArray() throws IOException { + endObject(); + return this; + } + + @Override + public MapObjectWriter beginObject() throws IOException { + stack.addLast(new HashMap<>()); + return this; + } + + @Override + public MapObjectWriter endObject() throws IOException { + final Object value = stack.removeLast(); + postValue(value); + return this; + } + + @Override + public MapObjectWriter value(final @Nullable String value) throws IOException { + postValue(value); + return this; + } + + @Override + public MapObjectWriter nullValue() throws IOException { + postValue((Object) null); + return this; + } + + @Override + public MapObjectWriter value(final boolean value) throws IOException { + postValue(value); + return this; + } + + @Override + public MapObjectWriter value(final @Nullable Boolean value) throws IOException { + postValue(value); + return this; + } + + @Override + public MapObjectWriter value(final double value) throws IOException { + postValue(value); + return this; + } + + @Override + public MapObjectWriter value(final long value) throws IOException { + postValue(value); + return this; + } + + @Override + public MapObjectWriter value(final @Nullable Number value) throws IOException { + postValue(value); + return this; + } + + private void serializeDate(final @NotNull ILogger logger, final @NotNull Date date) + throws IOException { + try { + value(DateUtils.getTimestamp(date)); + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "Error when serializing Date", e); + nullValue(); // Fallback to setting null when date is malformed. + } + } + + private void serializeTimeZone(final @NotNull ILogger logger, final @NotNull TimeZone timeZone) + throws IOException { + try { + value(timeZone.getID()); + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "Error when serializing TimeZone", e); + nullValue(); // Fallback. + } + } + + private void serializeCollection( + final @NotNull ILogger logger, final @NotNull Collection collection) throws IOException { + beginArray(); + for (Object object : collection) { + value(logger, object); + } + endArray(); + } + + private void serializeMap(final @NotNull ILogger logger, final @NotNull Map map) + throws IOException { + beginObject(); + for (Object key : map.keySet()) { + if (key instanceof String) { + name((String) key); + value(logger, map.get(key)); + } + } + endObject(); + } + + @SuppressWarnings("unchecked") + private void postValue(@Nullable Object value) { + final Object topStackElement = stack.peekLast(); + if (topStackElement instanceof List) { + // if top stack element is an array, value is an element within the array + ((List) topStackElement).add(value); + } else if (topStackElement instanceof String) { + // if top stack element is a String, it's the key for the value + // -> add both (key, value) to underlying map + final String key = (String) stack.removeLast(); + peekObject().put(key, value); + } else { + throw new IllegalStateException("Invalid stack state, expected array or string on top"); + } + } + + @SuppressWarnings("unchecked") + private @NotNull Map peekObject() { + final @Nullable Object item = stack.peekLast(); + if (item == null) { + throw new IllegalStateException("Stack is empty."); + } else if ((item instanceof Map)) { + return (Map) item; + } + throw new IllegalStateException("Stack element is not a Map."); + } +} diff --git a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt index b0f8e429f69..49608a86643 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt @@ -3,8 +3,8 @@ package io.sentry.protocol import io.sentry.ILogger import io.sentry.JsonDeserializer import io.sentry.JsonObjectReader -import io.sentry.JsonObjectWriter import io.sentry.JsonSerializable +import io.sentry.ObjectWriter import io.sentry.SentryBaseEvent import io.sentry.vendor.gson.stream.JsonToken import org.junit.Test @@ -17,7 +17,7 @@ class SentryBaseEventSerializationTest { * Make subclass, as `SentryBaseEvent` initializers are protected. */ class Sut : SentryBaseEvent(), JsonSerializable { - override fun serialize(writer: JsonObjectWriter, logger: ILogger) { + override fun serialize(writer: ObjectWriter, logger: ILogger) { writer.beginObject() Serializer().serialize(this, writer, logger) writer.endObject() diff --git a/sentry/src/test/java/io/sentry/util/MapObjectWriterTest.kt b/sentry/src/test/java/io/sentry/util/MapObjectWriterTest.kt new file mode 100644 index 00000000000..4127a8c840f --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/MapObjectWriterTest.kt @@ -0,0 +1,171 @@ +package io.sentry.util + +import io.sentry.ILogger +import io.sentry.JsonSerializable +import io.sentry.NoOpLogger +import io.sentry.ObjectWriter +import java.math.BigDecimal +import java.net.Inet4Address +import java.net.URI +import java.util.Calendar +import java.util.Currency +import java.util.Date +import java.util.Locale +import java.util.TimeZone +import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicIntegerArray +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertTrue + +class MapObjectWriterTest { + + enum class BasicEnum { + A + } + + class BasicSerializable : JsonSerializable { + override fun serialize(writer: ObjectWriter, logger: ILogger) { + writer.beginObject() + .name("key") + .value("value") + .endObject() + } + } + + @Test + fun `serializes data correctly`() { + val logger = NoOpLogger.getInstance() + + val data = mutableMapOf() + val writer = MapObjectWriter(data) + + writer.name("null").nullValue() + writer.name("int").value(1 as Int) + writer.name("boolean").value(true) + writer.name("long").value(Long.MAX_VALUE) + writer.name("double").value(Double.MAX_VALUE) + writer.name("number").value(BigDecimal(123)) + writer.name("date").value(logger, Date(0)) + writer.name("string").value("string") + + writer.name("TimeZone").value(logger, TimeZone.getTimeZone("Vienna")) + writer.name("JsonSerializable").value(logger, BasicSerializable()) + writer.name("Collection").value(logger, listOf("a", "b")) + writer.name("Arrays").value(logger, arrayOf("b", "c")) + writer.name("Map").value(logger, mapOf(kotlin.Pair("key", "value"))) + writer.name("Locale").value(logger, Locale.US) + writer.name("AtomicIntegerArray").value(logger, AtomicIntegerArray(intArrayOf(0, 1, 2))) + writer.name("AtomicBoolean").value(logger, AtomicBoolean(false)) + writer.name("URI").value(logger, URI.create("http://www.example.com")) + writer.name("InetAddress").value(logger, Inet4Address.getByName("1.1.1.1")) + writer.name("UUID").value(logger, UUID.fromString("00000000-1111-2222-3333-444444444444")) + writer.name("Currency").value(logger, Currency.getInstance("EUR")) + writer.name("Calendar").value( + logger, + Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { timeInMillis = 0 } + ) + writer.name("Enum").value(logger, BasicEnum.A) + + assertEquals(null, data["null"]) + assertEquals(1, data["int"]) + assertEquals(true, data["boolean"]) + assertEquals(Long.MAX_VALUE, data["long"]) + assertEquals(Double.MAX_VALUE, data["double"]) + assertEquals(BigDecimal(123), data["number"]) + assertEquals("1970-01-01T00:00:00.000Z", data["date"]) + assertEquals("string", data["string"]) + + assertEquals("GMT", data["TimeZone"]) + assertEquals( + mapOf( + kotlin.Pair("key", "value") + ), + data["JsonSerializable"] + ) + + assertEquals(listOf("a", "b"), data["Collection"]) + assertEquals(listOf("b", "c"), data["Arrays"]) + assertEquals(mapOf(kotlin.Pair("key", "value")), data["Map"]) + assertEquals("en_US", data["Locale"]) + assertEquals(listOf(0, 1, 2), data["AtomicIntegerArray"]) + assertEquals(false, data["AtomicBoolean"]) + assertEquals("http://www.example.com", data["URI"]) + assertEquals("/1.1.1.1", data["InetAddress"]) + assertEquals("00000000-1111-2222-3333-444444444444", data["UUID"]) + assertEquals("EUR", data["Currency"]) + assertEquals( + mapOf( + kotlin.Pair("month", 0), + kotlin.Pair("year", 1970), + kotlin.Pair("dayOfMonth", 1), + kotlin.Pair("hourOfDay", 0), + kotlin.Pair("minute", 0), + kotlin.Pair("second", 0) + ), + data["Calendar"] + ) + assertEquals("A", data["Enum"]) + } + + @Test + fun `serializes objects correctly`() { + val data = mutableMapOf() + val writer = MapObjectWriter(data) + + writer.name("object") + writer.beginObject() + writer.name("key") + writer.value("value") + writer.endObject() + + assertTrue(data.containsKey("object")) + assertEquals("value", (data["object"] as Map<*, *>)["key"]) + } + + @Test + fun `serializes nested arrays correctly`() { + val data = mutableMapOf() + val writer = MapObjectWriter(data) + + writer.name("array") + writer.beginArray() + + writer.beginArray() + writer.value("0") + writer.value("1") + writer.endArray() + + writer.value("2") + writer.endArray() + + assertTrue(data.containsKey("array")) + assertEquals(2, (data["array"] as List<*>).size) + assertEquals("0", ((data["array"] as List<*>)[0] as List<*>)[0]) + assertEquals("1", ((data["array"] as List<*>)[0] as List<*>)[1]) + assertEquals("2", (data["array"] as List<*>)[1]) + } + + @Test + fun `incorrect usage causes exception`() { + val data = mutableMapOf() + val writer = MapObjectWriter(data) + + assertFails { + // missing .beginObject() + writer.endObject() + } + + assertFails { + // missing .beginArray() + writer.endArray() + } + + assertFails { + // missing .name() + writer.value("value") + } + } +}