11package com.maxmind.device.collector
22
3- import android.app.ActivityManager
43import android.content.Context
5- import android.content.pm.PackageManager
6- import android.hardware.display.DisplayManager
7- import android.os.Build
8- import android.os.Environment
9- import android.os.StatFs
10- import android.util.DisplayMetrics
114import android.util.Log
12- import android.view.Display
5+ import com.maxmind.device.collector.helper.BuildInfoHelper
6+ import com.maxmind.device.collector.helper.DisplayInfoHelper
7+ import com.maxmind.device.collector.helper.HardwareInfoHelper
8+ import com.maxmind.device.collector.helper.InstallationInfoHelper
9+ import com.maxmind.device.collector.helper.LocaleInfoHelper
1310import com.maxmind.device.model.BuildInfo
1411import com.maxmind.device.model.DeviceData
1512import com.maxmind.device.model.DisplayInfo
@@ -18,7 +15,6 @@ import com.maxmind.device.model.InstallationInfo
1815import com.maxmind.device.model.LocaleInfo
1916import com.maxmind.device.model.StoredID
2017import com.maxmind.device.storage.StoredIDStorage
21- import java.util.Locale
2218import java.util.TimeZone
2319
2420/* *
@@ -30,53 +26,69 @@ import java.util.TimeZone
3026 * @param context Application context for accessing system services
3127 * @param storedIDStorage Optional storage for server-generated stored IDs
3228 * @param enableLogging Whether to log collection failures (defaults to false)
29+ * @param buildInfoHelper Helper for collecting build info (injectable for testing)
30+ * @param displayInfoHelper Helper for collecting display info (injectable for testing)
31+ * @param hardwareInfoHelper Helper for collecting hardware info (injectable for testing)
32+ * @param installationInfoHelper Helper for collecting installation info (injectable for testing)
33+ * @param localeInfoHelper Helper for collecting locale info (injectable for testing)
3334 */
35+ @Suppress(" LongParameterList" ) // Intentional for dependency injection/testing
3436internal class DeviceDataCollector (
35- private val context : Context ,
37+ context : Context ,
3638 storedIDStorage : StoredIDStorage ? = null ,
3739 private val enableLogging : Boolean = false ,
40+ private val buildInfoHelper : BuildInfoHelper = BuildInfoHelper (),
41+ private val displayInfoHelper : DisplayInfoHelper = DisplayInfoHelper (context),
42+ private val hardwareInfoHelper : HardwareInfoHelper = HardwareInfoHelper (context),
43+ private val installationInfoHelper : InstallationInfoHelper = InstallationInfoHelper (context),
44+ private val localeInfoHelper : LocaleInfoHelper = LocaleInfoHelper (),
3845) {
3946 private companion object {
4047 private const val TAG = " DeviceDataCollector"
4148
4249 // 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- )
50+ private val BUILD_INFO_FALLBACK =
51+ BuildInfo (
52+ fingerprint = " " ,
53+ manufacturer = " " ,
54+ model = " " ,
55+ brand = " " ,
56+ device = " " ,
57+ product = " " ,
58+ board = " " ,
59+ hardware = " " ,
60+ osVersion = " " ,
61+ sdkVersion = 0 ,
62+ )
63+
64+ private val DISPLAY_INFO_FALLBACK =
65+ DisplayInfo (
66+ widthPixels = 0 ,
67+ heightPixels = 0 ,
68+ densityDpi = 0 ,
69+ density = 0f ,
70+ )
71+
72+ private val HARDWARE_INFO_FALLBACK =
73+ HardwareInfo (
74+ cpuCores = 0 ,
75+ totalMemoryBytes = 0L ,
76+ totalStorageBytes = 0L ,
77+ )
78+
79+ private val INSTALLATION_INFO_FALLBACK =
80+ InstallationInfo (
81+ firstInstallTime = 0L ,
82+ lastUpdateTime = 0L ,
83+ versionCode = 0L ,
84+ )
85+
86+ private val LOCALE_INFO_FALLBACK =
87+ LocaleInfo (
88+ language = " " ,
89+ country = " " ,
90+ timezone = " " ,
91+ )
8092 }
8193
8294 private val storedIDCollector = storedIDStorage?.let { StoredIDCollector (it) }
@@ -104,7 +116,10 @@ internal class DeviceDataCollector(
104116 * @return The collected value or fallback on failure
105117 */
106118 @Suppress(" TooGenericExceptionCaught" )
107- private inline fun <T > collectSafe (fallback : T , block : () -> T ): T =
119+ private inline fun <T > collectSafe (
120+ fallback : T ,
121+ block : () -> T ,
122+ ): T =
108123 try {
109124 block()
110125 } catch (e: Exception ) {
@@ -123,143 +138,25 @@ internal class DeviceDataCollector(
123138 DeviceData (
124139 storedID = storedIDCollector?.collect() ? : StoredID (),
125140 deviceIDs = deviceIDsCollector.collect(),
126- build = collectSafe(BUILD_INFO_FALLBACK ) { collectBuildInfo () },
127- display = collectSafe(DISPLAY_INFO_FALLBACK ) { collectDisplayInfo() },
128- hardware = collectSafe(HARDWARE_INFO_FALLBACK ) { collectHardwareInfo () },
141+ build = collectSafe(BUILD_INFO_FALLBACK ) { buildInfoHelper.collect () },
142+ display = collectSafe(DISPLAY_INFO_FALLBACK ) { displayInfoHelper.collect() ? : DISPLAY_INFO_FALLBACK },
143+ hardware = collectSafe(HARDWARE_INFO_FALLBACK ) { hardwareInfoHelper.collect () },
129144 gpu = gpuCollector.collect(),
130145 audio = audioCollector.collect(),
131146 sensors = sensorCollector.collect(),
132147 cameras = cameraCollector.collect(),
133148 codecs = codecCollector.collect(),
134149 systemFeatures = systemFeaturesCollector.collect(),
135150 network = networkCollector.collect(),
136- installation = collectSafe(INSTALLATION_INFO_FALLBACK ) { collectInstallationInfo () },
151+ installation = collectSafe(INSTALLATION_INFO_FALLBACK ) { installationInfoHelper.collect () },
137152 settings = settingsCollector.collect(),
138153 behavior = behaviorCollector.collect(),
139154 telephony = telephonyCollector.collect(),
140155 fonts = fontCollector.collect(),
141- locale = collectSafe(LOCALE_INFO_FALLBACK ) { collectLocaleInfo () },
156+ locale = collectSafe(LOCALE_INFO_FALLBACK ) { localeInfoHelper.collect () },
142157 // Timezone offset in minutes (uses getOffset to account for DST)
143158 timezoneOffset = TimeZone .getDefault().getOffset(System .currentTimeMillis()) / 60000 ,
144159 deviceTime = System .currentTimeMillis(),
145160 webViewUserAgent = webViewCollector.collectUserAgent(),
146161 )
147-
148- private fun collectBuildInfo (): BuildInfo =
149- BuildInfo (
150- fingerprint = Build .FINGERPRINT ,
151- manufacturer = Build .MANUFACTURER ,
152- model = Build .MODEL ,
153- brand = Build .BRAND ,
154- device = Build .DEVICE ,
155- product = Build .PRODUCT ,
156- board = Build .BOARD ,
157- hardware = Build .HARDWARE ,
158- bootloader = Build .BOOTLOADER ,
159- osVersion = Build .VERSION .RELEASE ,
160- sdkVersion = Build .VERSION .SDK_INT ,
161- securityPatch = Build .VERSION .SECURITY_PATCH ,
162- supportedAbis = Build .SUPPORTED_ABIS .toList(),
163- )
164-
165- private fun collectDisplayInfo (): DisplayInfo {
166- val displayManager = context.getSystemService(Context .DISPLAY_SERVICE ) as ? DisplayManager
167- ? : return DISPLAY_INFO_FALLBACK
168-
169- val display = displayManager.getDisplay(Display .DEFAULT_DISPLAY )
170- ? : return DISPLAY_INFO_FALLBACK
171-
172- val displayMetrics = DisplayMetrics ()
173-
174- @Suppress(" DEPRECATION" )
175- display.getMetrics(displayMetrics)
176-
177- // Get refresh rate using modern API on Android R+
178- val refreshRate = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
179- display.mode.refreshRate
180- } else {
181- @Suppress(" DEPRECATION" )
182- display.refreshRate
183- }
184- windowManager.defaultDisplay.getMetrics(displayMetrics)
185- display.getMetrics(displayMetrics)
186-
187- // Get refresh rate using modern API on Android R+
188- val refreshRate = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
189- display.mode.refreshRate
190- } else {
191- @Suppress(" DEPRECATION" )
192- display.refreshRate
193- }
194-
195- // Collect HDR capabilities on Android N+ (API 24)
196- val hdrCapabilities = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
197- display.hdrCapabilities?.supportedHdrTypes?.toList()
198- } else {
199- null
200- }
201-
202- return DisplayInfo (
203- widthPixels = displayMetrics.widthPixels,
204- heightPixels = displayMetrics.heightPixels,
205- densityDpi = displayMetrics.densityDpi,
206- density = displayMetrics.density,
207- refreshRate = refreshRate,
208- hdrCapabilities = hdrCapabilities,
209- )
210- }
211-
212- private fun collectHardwareInfo (): HardwareInfo {
213- val activityManager = context.getSystemService(Context .ACTIVITY_SERVICE ) as ActivityManager
214- val memoryInfo = ActivityManager .MemoryInfo ()
215- activityManager.getMemoryInfo(memoryInfo)
216-
217- val statFs = StatFs (Environment .getDataDirectory().path)
218- val totalStorageBytes = statFs.blockCountLong * statFs.blockSizeLong
219-
220- return HardwareInfo (
221- cpuCores = Runtime .getRuntime().availableProcessors(),
222- totalMemoryBytes = memoryInfo.totalMem,
223- totalStorageBytes = totalStorageBytes,
224- )
225- }
226-
227- private fun collectInstallationInfo (): InstallationInfo {
228- val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0 )
229-
230- val versionCode = packageInfo.longVersionCode
231-
232- @Suppress(" SwallowedException" )
233- val installerPackage =
234- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
235- try {
236- context.packageManager.getInstallSourceInfo(context.packageName).installingPackageName
237- } catch (e: PackageManager .NameNotFoundException ) {
238- // Package not found is expected for some installation scenarios, return null
239- null
240- }
241- } else {
242- @Suppress(" DEPRECATION" )
243- context.packageManager.getInstallerPackageName(context.packageName)
244- }
245-
246- return InstallationInfo (
247- firstInstallTime = packageInfo.firstInstallTime,
248- lastUpdateTime = packageInfo.lastUpdateTime,
249- installerPackage = installerPackage,
250- versionCode = versionCode,
251- versionName = packageInfo.versionName,
252- )
253- }
254-
255- private fun collectLocaleInfo (): LocaleInfo {
256- val locale = Locale .getDefault()
257- val timezone = TimeZone .getDefault()
258-
259- return LocaleInfo (
260- language = locale.language,
261- country = locale.country,
262- timezone = timezone.id,
263- )
264- }
265162}
0 commit comments