@@ -8,6 +8,7 @@ import android.os.Build
88import android.os.Environment
99import android.os.StatFs
1010import android.util.DisplayMetrics
11+ import android.util.Log
1112import android.view.Display
1213import android.view.WindowManager
1314import 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 */
3335internal 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