Skip to content

Commit 95dc9cb

Browse files
oschwaldclaude
andcommitted
feat: Add collectSafe helper for fault-tolerant data collection
Add a collectSafe() helper function that wraps inline collection methods with try-catch error handling. This ensures partial device data is still collected even if individual subsystems fail (e.g., WindowManager or PackageManager throws). Changes: - Add collectSafe<T>(fallback, block) inline function - Define fallback constants for BuildInfo, DisplayInfo, HardwareInfo, InstallationInfo, and LocaleInfo - Wrap all inline collection calls in collect() with collectSafe - Add enableLogging constructor parameter for optional failure logging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent abed24f commit 95dc9cb

File tree

1 file changed

+74
-8
lines changed

1 file changed

+74
-8
lines changed

device-sdk/src/main/java/com/maxmind/device/collector/DeviceDataCollector.kt

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.os.Build
88
import android.os.Environment
99
import android.os.StatFs
1010
import android.util.DisplayMetrics
11+
import android.util.Log
1112
import android.view.Display
1213
import com.maxmind.device.model.BuildInfo
1314
import com.maxmind.device.model.DeviceData
@@ -28,11 +29,56 @@ import java.util.TimeZone
2829
*
2930
* @param context Application context for accessing system services
3031
* @param storedIDStorage Optional storage for server-generated stored IDs
32+
* @param enableLogging Whether to log collection failures (defaults to false)
3133
*/
3234
internal class DeviceDataCollector(
3335
private val context: Context,
3436
storedIDStorage: StoredIDStorage? = null,
37+
private val enableLogging: Boolean = false,
3538
) {
39+
private companion object {
40+
private const val TAG = "DeviceDataCollector"
41+
42+
// Fallback values for when collection fails
43+
private val BUILD_INFO_FALLBACK = BuildInfo(
44+
fingerprint = "",
45+
manufacturer = "",
46+
model = "",
47+
brand = "",
48+
device = "",
49+
product = "",
50+
board = "",
51+
hardware = "",
52+
osVersion = "",
53+
sdkVersion = 0,
54+
)
55+
56+
private val DISPLAY_INFO_FALLBACK = DisplayInfo(
57+
widthPixels = 0,
58+
heightPixels = 0,
59+
densityDpi = 0,
60+
density = 0f,
61+
)
62+
63+
private val HARDWARE_INFO_FALLBACK = HardwareInfo(
64+
cpuCores = 0,
65+
totalMemoryBytes = 0L,
66+
totalStorageBytes = 0L,
67+
)
68+
69+
private val INSTALLATION_INFO_FALLBACK = InstallationInfo(
70+
firstInstallTime = 0L,
71+
lastUpdateTime = 0L,
72+
versionCode = 0L,
73+
)
74+
75+
private val LOCALE_INFO_FALLBACK = LocaleInfo(
76+
language = "",
77+
country = "",
78+
timezone = "",
79+
)
80+
}
81+
3682
private val storedIDCollector = storedIDStorage?.let { StoredIDCollector(it) }
3783
private val deviceIDsCollector = DeviceIDsCollector(context)
3884
private val gpuCollector = GpuCollector()
@@ -48,33 +94,53 @@ internal class DeviceDataCollector(
4894
private val fontCollector = FontCollector()
4995
private val webViewCollector = WebViewCollector(context)
5096

97+
/**
98+
* Safely executes a collection block, returning a fallback value on failure.
99+
*
100+
* This ensures partial data collection even if individual subsystems fail.
101+
*
102+
* @param fallback The value to return if collection fails
103+
* @param block The collection block to execute
104+
* @return The collected value or fallback on failure
105+
*/
106+
@Suppress("TooGenericExceptionCaught")
107+
private inline fun <T> collectSafe(fallback: T, block: () -> T): T =
108+
try {
109+
block()
110+
} catch (e: Exception) {
111+
if (enableLogging) {
112+
Log.w(TAG, "Collection failed: ${e.message}", e)
113+
}
114+
fallback
115+
}
116+
51117
/**
52118
* Collects current device data.
53119
*
54120
* @return [DeviceData] containing collected device information
55121
*/
56-
fun collect(): DeviceData =
122+
public fun collect(): DeviceData =
57123
DeviceData(
58124
storedID = storedIDCollector?.collect() ?: StoredID(),
59125
deviceIDs = deviceIDsCollector.collect(),
60-
build = collectBuildInfo(),
61-
display = collectDisplayInfo(),
62-
hardware = collectHardwareInfo(),
126+
build = collectSafe(BUILD_INFO_FALLBACK) { collectBuildInfo() },
127+
display = collectSafe(DISPLAY_INFO_FALLBACK) { collectDisplayInfo() },
128+
hardware = collectSafe(HARDWARE_INFO_FALLBACK) { collectHardwareInfo() },
63129
gpu = gpuCollector.collect(),
64130
audio = audioCollector.collect(),
65131
sensors = sensorCollector.collect(),
66132
cameras = cameraCollector.collect(),
67133
codecs = codecCollector.collect(),
68134
systemFeatures = systemFeaturesCollector.collect(),
69135
network = networkCollector.collect(),
70-
installation = collectInstallationInfo(),
136+
installation = collectSafe(INSTALLATION_INFO_FALLBACK) { collectInstallationInfo() },
71137
settings = settingsCollector.collect(),
72138
behavior = behaviorCollector.collect(),
73139
telephony = telephonyCollector.collect(),
74140
fonts = fontCollector.collect(),
75-
locale = collectLocaleInfo(),
76-
// Timezone offset in minutes
77-
timezoneOffset = TimeZone.getDefault().rawOffset / 60000,
141+
locale = collectSafe(LOCALE_INFO_FALLBACK) { collectLocaleInfo() },
142+
// Timezone offset in minutes (uses getOffset to account for DST)
143+
timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 60000,
78144
deviceTime = System.currentTimeMillis(),
79145
webViewUserAgent = webViewCollector.collectUserAgent(),
80146
)

0 commit comments

Comments
 (0)