@@ -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 com.maxmind.device.model.BuildInfo
1314import 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 */
3234internal 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