diff --git a/app/src/main/java/com/stand/standapp/utils/DeviceInfoUtils.kt b/app/src/main/java/com/stand/standapp/utils/DeviceInfoUtils.kt new file mode 100644 index 0000000..d0c4ace --- /dev/null +++ b/app/src/main/java/com/stand/standapp/utils/DeviceInfoUtils.kt @@ -0,0 +1,217 @@ +package com.stand.standapp.utils + +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import android.os.Environment +import android.os.Process +import android.os.StatFs +import org.json.JSONObject +import timber.log.Timber +import java.net.NetworkInterface +import java.util.Collections +import java.util.Locale + +object DeviceInfoUtils { + + fun getDeviceInfoJson(context: Context): JSONObject { + val json = JSONObject() + val appContext = context.applicationContext + + // Base App info is typically set at the caller level, but we populate core details + try { + json.put("model", Build.MODEL) + json.put("osVersion", Build.VERSION.RELEASE) + + val abis = Build.SUPPORTED_ABIS + json.put("cpuAbi", abis.joinToString(",")) + + val is64Bit = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Process.is64Bit() + } else { + abis.any { it.contains("64") } + } + json.put("is64Bit", is64Bit) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get base device info") + } + + // 1. Screen and display info + try { + val screenObj = JSONObject() + val metrics = appContext.resources.displayMetrics + val widthPixels = metrics.widthPixels + val heightPixels = metrics.heightPixels + val xdpi = metrics.xdpi + val ydpi = metrics.ydpi + + screenObj.put("width", widthPixels) + screenObj.put("height", heightPixels) + screenObj.put("density", metrics.density) + screenObj.put("densityDpi", metrics.densityDpi) + + val physicalSize = if (xdpi > 0 && ydpi > 0) { + val widthInches = widthPixels / xdpi + val heightInches = heightPixels / ydpi + Math.sqrt((widthInches * widthInches + heightInches * heightInches).toDouble()) + } else { + 0.0 + } + screenObj.put("physicalSize", Math.round(physicalSize * 10.0) / 10.0) // 1 decimal place + + json.put("screen", screenObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get screen metrics") + } + + // 2. Hardware and manufacturer info + try { + val hwObj = JSONObject() + hwObj.put("manufacturer", Build.MANUFACTURER) + hwObj.put("brand", Build.BRAND) + hwObj.put("hardware", Build.HARDWARE) + hwObj.put("fingerprint", Build.FINGERPRINT) + json.put("hardware", hwObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get hardware info") + } + + // 3. System and Locale environment + try { + val sysObj = JSONObject() + sysObj.put("sdkVersion", Build.VERSION.SDK_INT) + sysObj.put("language", Locale.getDefault().language) + sysObj.put("country", Locale.getDefault().country) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + sysObj.put("securityPatch", Build.VERSION.SECURITY_PATCH) + } else { + sysObj.put("securityPatch", "") + } + json.put("system", sysObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get system info") + } + + // 4. Memory Info (RAM) + try { + val memObj = JSONObject() + val actManager = appContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val memInfo = ActivityManager.MemoryInfo() + actManager.getMemoryInfo(memInfo) + memObj.put("totalRam", memInfo.totalMem) + memObj.put("availRam", memInfo.availMem) + json.put("memory", memObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get memory info") + } + + // 5. Storage Info + try { + val storageObj = JSONObject() + val path = Environment.getDataDirectory() + val stat = StatFs(path.path) + val blockSize = stat.blockSizeLong + val totalBlocks = stat.blockCountLong + val availableBlocks = stat.availableBlocksLong + + storageObj.put("totalInternal", totalBlocks * blockSize) + storageObj.put("availInternal", availableBlocks * blockSize) + json.put("storage", storageObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get storage info") + } + + // 6. Network state + try { + val netObj = JSONObject() + val connManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + var networkType = "NONE" + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val activeNetwork = connManager.activeNetwork + val capabilities = connManager.getNetworkCapabilities(activeNetwork) + if (capabilities != null) { + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + networkType = "WIFI" + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + networkType = "CELLULAR" + } + } + } else { + @Suppress("DEPRECATION") + val activeNetworkInfo = connManager.activeNetworkInfo + if (activeNetworkInfo != null && activeNetworkInfo.isConnected) { + if (activeNetworkInfo.type == ConnectivityManager.TYPE_WIFI) { + networkType = "WIFI" + } else if (activeNetworkInfo.type == ConnectivityManager.TYPE_MOBILE) { + networkType = "CELLULAR" + } + } + } + netObj.put("type", networkType) + + var ipAddress = "" + try { + val interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()) + for (intf in interfaces) { + val addrs = Collections.list(intf.inetAddresses) + for (addr in addrs) { + if (!addr.isLoopbackAddress) { + val sAddr = addr.hostAddress ?: continue + val isIPv4 = sAddr.indexOf(':') < 0 + if (isIPv4) { + ipAddress = sAddr + break + } + } + } + if (ipAddress.isNotEmpty()) break + } + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").w(e, "Failed to resolve IP address") + } + netObj.put("ipAddress", ipAddress) + json.put("network", netObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get network status") + } + + // 7. Battery status + try { + val batObj = JSONObject() + val batteryStatus: Intent? = appContext.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + val level = batteryStatus?.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, -1) ?: -1 + val scale = batteryStatus?.getIntExtra(android.os.BatteryManager.EXTRA_SCALE, -1) ?: -1 + val batteryPct = if (level >= 0 && scale > 0) (level * 100 / scale.toFloat()).toInt() else -1 + batObj.put("level", batteryPct) + + val status = batteryStatus?.getIntExtra(android.os.BatteryManager.EXTRA_STATUS, -1) ?: -1 + val statusString = when (status) { + android.os.BatteryManager.BATTERY_STATUS_CHARGING -> "charging" + android.os.BatteryManager.BATTERY_STATUS_DISCHARGING -> "discharging" + android.os.BatteryManager.BATTERY_STATUS_FULL -> "full" + android.os.BatteryManager.BATTERY_STATUS_NOT_CHARGING -> "not_charging" + else -> "unknown" + } + batObj.put("status", statusString) + + val chargePlug = batteryStatus?.getIntExtra(android.os.BatteryManager.EXTRA_PLUGGED, -1) ?: -1 + val plugString = when (chargePlug) { + android.os.BatteryManager.BATTERY_PLUGGED_AC -> "ac" + android.os.BatteryManager.BATTERY_PLUGGED_USB -> "usb" + android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS -> "wireless" + else -> "none" + } + batObj.put("plugged", plugString) + json.put("battery", batObj) + } catch (e: Exception) { + Timber.tag("DeviceInfoUtils").e(e, "Failed to get battery info") + } + + return json + } +} \ No newline at end of file