package io.sentry.android.core; import static android.content.Context.ACTIVITY_SERVICE; import static android.os.BatteryManager.EXTRA_TEMPERATURE; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.res.AssetManager; import android.os.BatteryManager; import android.os.Build; import android.os.Environment; import android.os.LocaleList; import android.os.Looper; import android.os.StatFs; import android.os.SystemClock; import android.provider.Settings; import android.util.DisplayMetrics; import io.sentry.android.core.util.ConnectivityChecker; import io.sentry.android.core.util.DeviceOrientations; import io.sentry.android.core.util.RootChecker; import io.sentry.core.DateUtils; import io.sentry.core.EventProcessor; import io.sentry.core.ILogger; import io.sentry.core.SentryEvent; import io.sentry.core.SentryLevel; import io.sentry.core.SentryOptions; import io.sentry.core.protocol.App; import io.sentry.core.protocol.DebugImage; import io.sentry.core.protocol.DebugMeta; import io.sentry.core.protocol.Device; import io.sentry.core.protocol.OperatingSystem; import io.sentry.core.protocol.SdkVersion; import io.sentry.core.protocol.SentryThread; import io.sentry.core.protocol.User; import io.sentry.core.util.ApplyScopeUtils; import io.sentry.core.util.Objects; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; final class DefaultAndroidEventProcessor implements EventProcessor { @TestOnly static final String PROGUARD_UUID = "proGuardUuids"; @TestOnly static final String ROOTED = "rooted"; @TestOnly static final String ANDROID_ID = "androidId"; @TestOnly static final String KERNEL_VERSION = "kernelVersion"; @TestOnly static final String EMULATOR = "emulator"; // it could also be a parameter and get from Sentry.init(...) private static final Date appStartTime = DateUtils.getCurrentDateTime(); @TestOnly final Context context; private final SentryOptions options; @TestOnly final Future<Map<String, Object>> contextData; private final @NotNull IBuildInfoProvider buildInfoProvider; private final @NotNull RootChecker rootChecker; private final @NotNull ILogger logger; public DefaultAndroidEventProcessor( final @NotNull Context context, final @NotNull SentryOptions options, final @NotNull IBuildInfoProvider buildInfoProvider) { this( context, options, buildInfoProvider, new RootChecker(context, buildInfoProvider, options.getLogger())); } DefaultAndroidEventProcessor( final @NotNull Context context, final @NotNull SentryOptions options, final @NotNull IBuildInfoProvider buildInfoProvider, final @NotNull RootChecker rootChecker) { this.context = Objects.requireNonNull(context, "The application context is required."); this.options = Objects.requireNonNull(options, "The SentryOptions is required."); this.logger = Objects.requireNonNull(options.getLogger(), "The Logger is required."); this.buildInfoProvider = Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required."); this.rootChecker = Objects.requireNonNull(rootChecker, "The RootChecker is required."); ExecutorService executorService = Executors.newSingleThreadExecutor(); // dont ref. to method reference, theres a bug on it contextData = executorService.submit(() -> loadContextData()); executorService.shutdown(); } private @NotNull Map<String, Object> loadContextData() { Map<String, Object> map = new HashMap<>(); String[] proguardUUIDs = getProguardUUIDs(); if (proguardUUIDs != null) { map.put(PROGUARD_UUID, proguardUUIDs); } map.put(ROOTED, rootChecker.isDeviceRooted()); String androidId = getAndroidId(); if (androidId != null) { map.put(ANDROID_ID, androidId); } String kernelVersion = getKernelVersion(); 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, isEmulator()); return map; } @Override public @NotNull SentryEvent process( final @NotNull SentryEvent event, final @Nullable Object hint) { if (ApplyScopeUtils.shouldApplyScopeData(hint)) { processNonCachedEvent(event); } else { logger.log( SentryLevel.DEBUG, "Event was cached so not applying data relevant to the current app execution/version: %s", event.getEventId()); } if (event.getContexts().getDevice() == null) { event.getContexts().setDevice(getDevice()); } if (event.getContexts().getOperatingSystem() == null) { event.getContexts().setOperatingSystem(getOperatingSystem()); } return event; } // Data to be applied to events that was created in the running process private void processNonCachedEvent(final @NotNull SentryEvent event) { if (event.getUser() == null) { event.setUser(getUser()); } App app = event.getContexts().getApp(); if (app == null) { app = new App(); } setAppExtras(app); if (event.getDebugMeta() == null) { event.setDebugMeta(getDebugMeta()); } if (event.getSdk() == null) { event.setSdk(getSdkVersion()); } PackageInfo packageInfo = ContextUtils.getPackageInfo(context, logger); if (packageInfo != null) { String versionCode = ContextUtils.getVersionCode(packageInfo); if (event.getDist() == null) { event.setDist(versionCode); } setAppPackageInfo(app, packageInfo); } event.getContexts().setApp(app); if (event.getThreads() != null) { for (SentryThread thread : event.getThreads()) { thread.setCurrent(Looper.getMainLooper().getThread().getId() == thread.getId()); } } } private @Nullable List<DebugImage> getDebugImages() { String[] proguardUUIDs = null; try { Object proguardUUIDsObject = contextData.get().get(PROGUARD_UUID); if (proguardUUIDsObject != null) { proguardUUIDs = (String[]) proguardUUIDsObject; } } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting Proguard UUIDs.", e); return null; } if (proguardUUIDs == null || proguardUUIDs.length == 0) { return null; } List<DebugImage> images = new ArrayList<>(); for (String item : proguardUUIDs) { DebugImage debugImage = new DebugImage(); debugImage.setType("proguard"); debugImage.setUuid(item); images.add(debugImage); } return images; } private @Nullable DebugMeta getDebugMeta() { List<DebugImage> debugImages = getDebugImages(); if (debugImages == null) { return null; } DebugMeta debugMeta = new DebugMeta(); debugMeta.setImages(debugImages); return debugMeta; } private void setAppExtras(final @NotNull App app) { app.setAppName(getApplicationName()); app.setAppStartTime(appStartTime); } private @NotNull SdkVersion getSdkVersion() { SdkVersion sdkVersion = new SdkVersion(); sdkVersion.setName("sentry.java.android"); String version = BuildConfig.VERSION_NAME; sdkVersion.setVersion(version); sdkVersion.addPackage("maven:sentry-core", version); sdkVersion.addPackage("maven:sentry-android-core", version); if (options.isEnableNdk()) { sdkVersion.addPackage("maven:sentry-android-ndk", version); } return sdkVersion; } @SuppressWarnings("deprecation") private @NotNull String getAbi() { return Build.CPU_ABI; } @SuppressWarnings("deprecation") private @NotNull String getAbi2() { return Build.CPU_ABI2; } @SuppressWarnings({"ObsoleteSdkInt", "deprecation"}) private void setArchitectures(final @NotNull Device device) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { String[] supportedAbis = Build.SUPPORTED_ABIS; device.setArch(supportedAbis[0]); device.setArchs(supportedAbis); } else { String[] supportedAbis = {getAbi(), getAbi2()}; device.setArch(supportedAbis[0]); device.setArchs(supportedAbis); // we were not checking CPU_ABI2, but I've added to the list now } } @SuppressWarnings("ObsoleteSdkInt") private @NotNull Long getMemorySize(final @NotNull ActivityManager.MemoryInfo memInfo) { if (Build.VERSION.SDK_INT >= 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() { // TODO: missing usable memory Device device = new Device(); device.setName(getDeviceName()); device.setManufacturer(Build.MANUFACTURER); device.setBrand(Build.BRAND); device.setFamily(getFamily()); device.setModel(Build.MODEL); device.setModelId(Build.ID); setArchitectures(device); Intent batteryIntent = getBatteryIntent(); if (batteryIntent != null) { device.setBatteryLevel(getBatteryLevel(batteryIntent)); device.setCharging(isCharging(batteryIntent)); device.setBatteryTemperature(getBatteryTemperature(batteryIntent)); } Boolean connected; switch (ConnectivityChecker.getConnectionStatus(context, logger)) { case NOT_CONNECTED: connected = false; break; case CONNECTED: connected = true; break; default: connected = null; } device.setOnline(connected); device.setOrientation(getOrientation()); try { Object emulator = contextData.get().get(EMULATOR); if (emulator != null) { device.setSimulator((Boolean) emulator); } } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting emulator.", e); } ActivityManager.MemoryInfo memInfo = getMemInfo(); if (memInfo != null) { // in bytes device.setMemorySize(getMemorySize(memInfo)); 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 File internalStorageFile = context.getExternalFilesDir(null); if (internalStorageFile != null) { StatFs internalStorageStat = new StatFs(internalStorageFile.getPath()); device.setStorageSize(getTotalInternalStorage(internalStorageStat)); device.setFreeStorage(getUnusedInternalStorage(internalStorageStat)); } StatFs externalStorageStat = getExternalStorageStat(internalStorageFile); if (externalStorageStat != null) { device.setExternalStorageSize(getTotalExternalStorage(externalStorageStat)); device.setExternalFreeStorage(getUnusedExternalStorage(externalStorageStat)); } DisplayMetrics displayMetrics = getDisplayMetrics(); if (displayMetrics != null) { setScreenResolution(device, displayMetrics); 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()); } if (device.getLanguage() == null) { device.setLanguage(Locale.getDefault().toString()); // eg en_US } if (device.getConnectionType() == null) { // wifi, ethernet or cellular, null if none device.setConnectionType( ConnectivityChecker.getConnectionType(context, logger, buildInfoProvider)); } return device; } @SuppressWarnings("ObsoleteSdkInt") private @Nullable String getDeviceName() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { return Settings.Global.getString(context.getContentResolver(), "device_name"); } else { return null; } } @SuppressWarnings("deprecation") private void setScreenResolution( final @NotNull Device device, final @NotNull DisplayMetrics displayMetrics) { device.setScreenResolution(getResolution(displayMetrics)); } private TimeZone getTimeZone() { if (Build.VERSION.SDK_INT >= 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 @NotNull Date getBootTime() { // if user changes time, will give a wrong answer, consider ACTION_TIME_CHANGED return DateUtils.getDateTime( new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime())); } private @NotNull String getResolution(final @NotNull DisplayMetrics displayMetrics) { int largestSide = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); int smallestSide = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); return largestSide + "x" + smallestSide; } /** * Get MemoryInfo object representing the memory state of the application. * * @return MemoryInfo object representing the memory state of the application */ private @Nullable ActivityManager.MemoryInfo getMemInfo() { try { ActivityManager actManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE); ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); if (actManager != null) { actManager.getMemoryInfo(memInfo); return memInfo; } logger.log(SentryLevel.INFO, "Error getting MemoryInfo."); return null; } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting MemoryInfo.", e); return null; } } private @Nullable Intent getBatteryIntent() { return context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } /** * Fake the device family by using the first word in the Build.MODEL. Works well in most cases... * "Nexus 6P" -> "Nexus", "Galaxy S7" -> "Galaxy". * * @return family name of the device, as best we can tell */ private @Nullable String getFamily() { try { return Build.MODEL.split(" ", -1)[0]; } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting device family.", e); return null; } } /** * 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 (Exception e) { logger.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 (Exception e) { logger.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 (Exception e) { logger.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) { logger.log( SentryLevel.INFO, "No device orientation available (ORIENTATION_SQUARE|ORIENTATION_UNDEFINED)"); return null; } } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting device orientation.", e); } return deviceOrientation; } /** * Check whether the application is running in an emulator. * https://github.com/flutter/plugins/blob/master/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java#L105 * * @return true if the application is running in an emulator, false otherwise */ private @Nullable Boolean isEmulator() { try { return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) || Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("unknown") || Build.HARDWARE.contains("goldfish") || Build.HARDWARE.contains("ranchu") || Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator") || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion") || Build.PRODUCT.contains("sdk_google") || Build.PRODUCT.contains("google_sdk") || Build.PRODUCT.contains("sdk") || Build.PRODUCT.contains("sdk_x86") || Build.PRODUCT.contains("vbox86p") || Build.PRODUCT.contains("emulator") || Build.PRODUCT.contains("simulator"); } catch (Exception e) { logger.log( SentryLevel.ERROR, "Error checking whether application is running in an emulator.", e); return null; } } /** * 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 (Exception e) { logger.log(SentryLevel.ERROR, "Error getting total internal storage amount.", e); return null; } } @SuppressWarnings("ObsoleteSdkInt") private long getBlockSizeLong(final @NotNull StatFs stat) { if (Build.VERSION.SDK_INT >= 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") private long getBlockCountLong(final @NotNull StatFs stat) { if (Build.VERSION.SDK_INT >= 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") private long getAvailableBlocksLong(final @NotNull StatFs stat) { if (Build.VERSION.SDK_INT >= 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 (Exception e) { logger.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()); } logger.log(SentryLevel.INFO, "Not possible to read external files directory"); return null; } logger.log(SentryLevel.INFO, "External storage is not mounted or emulated."); return null; } @SuppressWarnings("ObsoleteSdkInt") private @Nullable File[] getExternalFilesDirs() { if (Build.VERSION.SDK_INT >= 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 { logger.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 (Exception e) { logger.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 (Exception e) { logger.log(SentryLevel.ERROR, "Error getting unused external storage amount.", e); return null; } } /** * Get the DisplayMetrics object for the current application. * * @return the DisplayMetrics object for the current application */ private @Nullable DisplayMetrics getDisplayMetrics() { try { return context.getResources().getDisplayMetrics(); } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting DisplayMetrics.", 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 (Exception e) { logger.log(SentryLevel.ERROR, "Error getting OperatingSystem.", e); } return os; } private void setAppPackageInfo(final @NotNull App app, final @NotNull PackageInfo packageInfo) { app.setAppIdentifier(packageInfo.packageName); app.setAppVersion(packageInfo.versionName); app.setAppBuild(ContextUtils.getVersionCode(packageInfo)); } /** * Get the device's current kernel version, as a string. Attempts to read /proc/version, and falls * back to the 'os.version' System Property. * * @return the device's current kernel version, as a string */ @SuppressWarnings("DefaultCharset") private @Nullable String getKernelVersion() { // its possible to try to execute 'uname' and parse it or also another unix commands or even // looking for well known root installed apps String errorMsg = "Exception while attempting to read kernel information"; String defaultVersion = System.getProperty("os.version"); File file = new File("/proc/version"); if (!file.canRead()) { return defaultVersion; } try (BufferedReader br = new BufferedReader(new FileReader(file))) { return br.readLine(); } catch (IOException e) { logger.log(SentryLevel.ERROR, errorMsg, e); } return defaultVersion; } /** * Get the human-facing Application name. * * @return Application name */ private @Nullable String getApplicationName() { try { ApplicationInfo applicationInfo = context.getApplicationInfo(); int stringId = applicationInfo.labelRes; if (stringId == 0) { if (applicationInfo.nonLocalizedLabel != null) { return applicationInfo.nonLocalizedLabel.toString(); } return context.getPackageManager().getApplicationLabel(applicationInfo).toString(); } else { return context.getString(stringId); } } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting application name.", e); } return null; } public @NotNull User getUser() { User user = new User(); user.setId(getDeviceId()); return user; } private @Nullable String getDeviceId() { try { Object androidId = contextData.get().get(ANDROID_ID); if (androidId != null) { return (String) androidId; } } catch (Exception e) { logger.log(SentryLevel.ERROR, "Error getting androidId.", e); } return null; } @SuppressWarnings("HardwareIds") private @Nullable String getAndroidId() { // Android 29 has changed and -> Avoid using hardware identifiers, find another way in the // future String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); // https://android-developers.googleblog.com/2011/03/identifying-app-installations.html if (androidId == null || androidId.isEmpty() || androidId.toLowerCase(Locale.ROOT).contentEquals("9774d56d682e549c")) { try { androidId = Installation.id(context); } catch (RuntimeException e) { logger.log(SentryLevel.ERROR, "Could not generate device Id.", e); return null; } } return androidId; } private @Nullable String[] getProguardUUIDs() { final AssetManager assets = context.getAssets(); // one may have thousands of asset files and looking up this list might slow down the SDK init. // quite a bit, for this reason, we try to open the file directly and take care of errors // like FileNotFoundException try (final InputStream is = new BufferedInputStream(assets.open("sentry-debug-meta.properties"))) { final Properties properties = new Properties(); properties.load(is); final String uuid = properties.getProperty("io.sentry.ProguardUuids"); if (uuid != null && !uuid.isEmpty()) { final String[] proguardUUIDs = uuid.split("\\|", -1); // it should be only 1 proguard uuid, but the API accepts an array so we are keeping it for // consistency for (final String item : proguardUUIDs) { logger.log(SentryLevel.DEBUG, "Proguard UUID found: %s", item); } return proguardUUIDs; } logger.log( SentryLevel.INFO, "io.sentry.ProguardUuids property was not found or it is invalid."); } catch (FileNotFoundException e) { logger.log(SentryLevel.INFO, "sentry-debug-meta.properties file was not found."); } catch (IOException e) { logger.log(SentryLevel.ERROR, "Error getting Proguard UUIDs.", e); } catch (RuntimeException e) { logger.log(SentryLevel.ERROR, "sentry-debug-meta.properties file is malformed.", e); } return null; } }