Skip to content

Commit 51cc67e

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 73fa117 commit 51cc67e

File tree

1 file changed

+72
-6
lines changed

1 file changed

+72
-6
lines changed

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

Lines changed: 72 additions & 6 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 android.view.WindowManager
1314
import com.maxmind.device.model.BuildInfo
@@ -29,11 +30,56 @@ import java.util.TimeZone
2930
*
3031
* @param context Application context for accessing system services
3132
* @param storedIDStorage Optional storage for server-generated stored IDs
33+
* @param enableLogging Whether to log collection failures (defaults to false)
3234
*/
3335
internal class DeviceDataCollector(
3436
private val context: Context,
3537
storedIDStorage: StoredIDStorage? = null,
38+
private val enableLogging: Boolean = false,
3639
) {
40+
private companion object {
41+
private const val TAG = "DeviceDataCollector"
42+
43+
// Fallback values for when collection fails
44+
private val BUILD_INFO_FALLBACK = BuildInfo(
45+
fingerprint = "",
46+
manufacturer = "",
47+
model = "",
48+
brand = "",
49+
device = "",
50+
product = "",
51+
board = "",
52+
hardware = "",
53+
osVersion = "",
54+
sdkVersion = 0,
55+
)
56+
57+
private val DISPLAY_INFO_FALLBACK = DisplayInfo(
58+
widthPixels = 0,
59+
heightPixels = 0,
60+
densityDpi = 0,
61+
density = 0f,
62+
)
63+
64+
private val HARDWARE_INFO_FALLBACK = HardwareInfo(
65+
cpuCores = 0,
66+
totalMemoryBytes = 0L,
67+
totalStorageBytes = 0L,
68+
)
69+
70+
private val INSTALLATION_INFO_FALLBACK = InstallationInfo(
71+
firstInstallTime = 0L,
72+
lastUpdateTime = 0L,
73+
versionCode = 0L,
74+
)
75+
76+
private val LOCALE_INFO_FALLBACK = LocaleInfo(
77+
language = "",
78+
country = "",
79+
timezone = "",
80+
)
81+
}
82+
3783
private val storedIDCollector = storedIDStorage?.let { StoredIDCollector(it) }
3884
private val deviceIDsCollector = DeviceIDsCollector(context)
3985
private val gpuCollector = GpuCollector()
@@ -49,31 +95,51 @@ internal class DeviceDataCollector(
4995
private val fontCollector = FontCollector()
5096
private val webViewCollector = WebViewCollector(context)
5197

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

0 commit comments

Comments
 (0)