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,11 +26,21 @@ 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 */
3435internal class DeviceDataCollector (
35- private val context : Context ,
36+ context : Context ,
3637 storedIDStorage : StoredIDStorage ? = null ,
3738 private val enableLogging : Boolean = false ,
39+ private val buildInfoHelper : BuildInfoHelper = BuildInfoHelper (),
40+ private val displayInfoHelper : DisplayInfoHelper = DisplayInfoHelper (context),
41+ private val hardwareInfoHelper : HardwareInfoHelper = HardwareInfoHelper (context),
42+ private val installationInfoHelper : InstallationInfoHelper = InstallationInfoHelper (context),
43+ private val localeInfoHelper : LocaleInfoHelper = LocaleInfoHelper (),
3844) {
3945 private companion object {
4046 private const val TAG = " DeviceDataCollector"
@@ -123,143 +129,25 @@ internal class DeviceDataCollector(
123129 DeviceData (
124130 storedID = storedIDCollector?.collect() ? : StoredID (),
125131 deviceIDs = deviceIDsCollector.collect(),
126- build = collectSafe(BUILD_INFO_FALLBACK ) { collectBuildInfo () },
127- display = collectSafe(DISPLAY_INFO_FALLBACK ) { collectDisplayInfo() },
128- hardware = collectSafe(HARDWARE_INFO_FALLBACK ) { collectHardwareInfo () },
132+ build = collectSafe(BUILD_INFO_FALLBACK ) { buildInfoHelper.collect () },
133+ display = collectSafe(DISPLAY_INFO_FALLBACK ) { displayInfoHelper.collect() ? : DISPLAY_INFO_FALLBACK },
134+ hardware = collectSafe(HARDWARE_INFO_FALLBACK ) { hardwareInfoHelper.collect () },
129135 gpu = gpuCollector.collect(),
130136 audio = audioCollector.collect(),
131137 sensors = sensorCollector.collect(),
132138 cameras = cameraCollector.collect(),
133139 codecs = codecCollector.collect(),
134140 systemFeatures = systemFeaturesCollector.collect(),
135141 network = networkCollector.collect(),
136- installation = collectSafe(INSTALLATION_INFO_FALLBACK ) { collectInstallationInfo () },
142+ installation = collectSafe(INSTALLATION_INFO_FALLBACK ) { installationInfoHelper.collect () },
137143 settings = settingsCollector.collect(),
138144 behavior = behaviorCollector.collect(),
139145 telephony = telephonyCollector.collect(),
140146 fonts = fontCollector.collect(),
141- locale = collectSafe(LOCALE_INFO_FALLBACK ) { collectLocaleInfo () },
147+ locale = collectSafe(LOCALE_INFO_FALLBACK ) { localeInfoHelper.collect () },
142148 // Timezone offset in minutes (uses getOffset to account for DST)
143149 timezoneOffset = TimeZone .getDefault().getOffset(System .currentTimeMillis()) / 60000 ,
144150 deviceTime = System .currentTimeMillis(),
145151 webViewUserAgent = webViewCollector.collectUserAgent(),
146152 )
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- }
265153}
0 commit comments