diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 555bd02..f604995 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ apiVersion = "82" datastore = "1.1.7" datastorePreferences = "1.1.7" googleSymbolProcessingApi = "2.0.0-1.0.24" -gson = "2.13.1" +gson = "2.13.2" jsonVersion = "20250517" kotlin = "2.0.0" coreKtx = "1.16.0" diff --git a/myphoneinfo/andinfo/build.gradle.kts b/myphoneinfo/andinfo/build.gradle.kts index 9aeed01..34ba0b3 100644 --- a/myphoneinfo/andinfo/build.gradle.kts +++ b/myphoneinfo/andinfo/build.gradle.kts @@ -50,6 +50,8 @@ dependencies { implementation(libs.androidx.datastore.preferences) // implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7") implementation(libs.androidx.datastore) + implementation(libs.squareup.okhttp) + implementation(libs.gson) // implementation("androidx.datastore:datastore-rxjava3:1.1.7") implementation("androidx.security:security-state:1.0.0-alpha04") testImplementation(libs.junit) diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BluetoothInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BluetoothInfo.kt index fba915e..01eb2b7 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BluetoothInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BluetoothInfo.kt @@ -2,44 +2,517 @@ package com.xyzshell.andinfo.libs import android.Manifest import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager +import android.bluetooth.le.BluetoothLeScanner +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.content.pm.PackageManager import android.os.Build +import androidx.core.app.ActivityCompat +/** + * 蓝牙信息工具类 + * 提供蓝牙适配器信息、已配对设备、附近设备扫描、蓝牙特性检测等功能 + */ class BluetoothInfo(private val context: Context) { + /** + * 蓝牙管理器 + */ private val bluetoothManager: BluetoothManager? = context.getSystemService( BluetoothManager::class.java ) + /** + * 蓝牙适配器 + */ @get:SuppressLint("MissingPermission") val bluetoothAdapter by lazy { bluetoothManager?.adapter } + /** + * 蓝牙设备信息数据类 + */ + data class DeviceInfo( + val name: String?, // 设备名称 + val address: String, // MAC地址 + val bondState: Int, // 配对状态 + val bondStateText: String, // 配对状态文本 + val deviceClass: Int?, // 设备类型 + val deviceType: Int?, // 设备类型(LE/Classic/Dual) + val rssi: Int? = null, // 信号强度(仅扫描设备) + val uuids: List? = null // 支持的服务UUID + ) + + /** + * 蓝牙特性信息数据类 + */ + data class BluetoothFeatures( + val version: String, // 蓝牙版本 + val bluetoothLe: Boolean, // 是否支持BLE + val bluetooth: Boolean, // 是否支持经典蓝牙 + + // Bluetooth 4.x 特性 + val le2MbPhy: Boolean, // 2Mbps PHY + val leCodedPhy: Boolean, // Coded PHY + val leExtendedAdvertising: Boolean, // 扩展广播 + val lePeriodicAdvertising: Boolean, // 周期性广播 + + // Bluetooth 5.x 特性 + val bluetooth5Support: Boolean, // Bluetooth 5支持 + val leAudioSupport: Boolean, // LE Audio支持 + val leAudioBroadcast: Boolean, // LE Audio广播 + val leAudioUnicast: Boolean, // LE Audio单播 + + // 其他特性 + val offloadedFiltering: Boolean, // 硬件过滤 + val offloadedScanBatching: Boolean, // 批量扫描 + val multipleAdvertisement: Boolean, // 多广播 + val lowEnergyExtended: Boolean // 扩展LE + ) + + // ==================== 基本信息 ==================== + + /** + * 获取蓝牙适配器名称 + * 需要权限: BLUETOOTH_CONNECT (Android 12+) + */ @get:SuppressLint("MissingPermission") val adapterName: String? - get() = bluetoothAdapter?.name + get() = if (checkBluetoothConnectPermission()) { + bluetoothAdapter?.name + } else null + /** + * 获取蓝牙适配器MAC地址 + * 需要权限: BLUETOOTH_CONNECT (Android 12+) + * 注意: Android 6.0+ 返回固定地址 02:00:00:00:00:00 + */ @get:SuppressLint("HardwareIds", "MissingPermission") val adapterMacAddress: String? - get() = bluetoothAdapter?.address + get() = if (checkBluetoothConnectPermission()) { + bluetoothAdapter?.address + } else null - @get:SuppressLint("MissingPermission") - val bondedDevices: List> - get() = bluetoothAdapter?.bondedDevices?.map { - Pair(it.name, it.address) - } ?: emptyList() + /** + * 蓝牙是否已启用 + */ + val isEnabled: Boolean + get() = bluetoothAdapter?.isEnabled == true + /** + * 蓝牙适配器状态 + */ + val state: Int + get() = bluetoothAdapter?.state ?: BluetoothAdapter.STATE_OFF + + /** + * 蓝牙适配器状态文本 + */ + val stateText: String + get() = when (state) { + BluetoothAdapter.STATE_OFF -> "关闭" + BluetoothAdapter.STATE_TURNING_ON -> "正在开启" + BluetoothAdapter.STATE_ON -> "已开启" + BluetoothAdapter.STATE_TURNING_OFF -> "正在关闭" + else -> "未知" + } + + /** + * 是否支持低功耗蓝牙(BLE) + */ val isBluetoothLeSupported: Boolean get() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) + /** + * 是否正在扫描设备 + */ + @get:SuppressLint("MissingPermission") + val isDiscovering: Boolean + get() = if (checkBluetoothScanPermission()) { + bluetoothAdapter?.isDiscovering == true + } else false + + // ==================== 已配对设备 ==================== + + /** + * 获取已配对设备列表(简化版本,兼容旧代码) + * 需要权限: BLUETOOTH_CONNECT (Android 12+) + */ + @get:SuppressLint("MissingPermission") + val bondedDevices: List> + get() = if (checkBluetoothConnectPermission()) { + bluetoothAdapter?.bondedDevices?.map { + Pair(it.name, it.address) + } ?: emptyList() + } else emptyList() + + /** + * 获取已配对设备详细信息列表 + * 需要权限: BLUETOOTH_CONNECT (Android 12+) + */ + @SuppressLint("MissingPermission") + fun getBondedDevicesInfo(): List { + if (!checkBluetoothConnectPermission()) return emptyList() + + return try { + bluetoothAdapter?.bondedDevices?.map { device -> + DeviceInfo( + name = device.name, + address = device.address, + bondState = device.bondState, + bondStateText = getBondStateText(device.bondState), + deviceClass = device.bluetoothClass?.deviceClass, + deviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + device.type + } else null, + uuids = device.uuids?.map { it.toString() } + ) + } ?: emptyList() + } catch (e: SecurityException) { + emptyList() + } + } + + // ==================== 附近设备扫描 ==================== + + /** + * 扫描附近的经典蓝牙设备 + * 需要权限: BLUETOOTH_SCAN, ACCESS_FINE_LOCATION (Android 12+) + * @param onDeviceFound 发现设备回调 + * @param onScanFinished 扫描完成回调 + */ + @SuppressLint("MissingPermission") + fun scanNearbyDevices( + onDeviceFound: (DeviceInfo) -> Unit, + onScanFinished: () -> Unit + ) { + if (!checkBluetoothScanPermission() || !checkLocationPermission()) { + onScanFinished() + return + } + + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + BluetoothDevice.ACTION_FOUND -> { + val device: BluetoothDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) + } + val rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE).toInt() + + device?.let { + onDeviceFound( + DeviceInfo( + name = it.name, + address = it.address, + bondState = it.bondState, + bondStateText = getBondStateText(it.bondState), + deviceClass = it.bluetoothClass?.deviceClass, + deviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + it.type + } else null, + rssi = rssi, + uuids = it.uuids?.map { uuid -> uuid.toString() } + ) + ) + } + } + BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> { + context.unregisterReceiver(this) + onScanFinished() + } + } + } + } + + val filter = IntentFilter().apply { + addAction(BluetoothDevice.ACTION_FOUND) + addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) + } + context.registerReceiver(receiver, filter) + bluetoothAdapter?.startDiscovery() + } + + /** + * 扫描附近的BLE设备 + * 需要权限: BLUETOOTH_SCAN, ACCESS_FINE_LOCATION (Android 12+) + * @param onDeviceFound 发现设备回调 + * @param durationMillis 扫描时长(毫秒) + */ + @SuppressLint("MissingPermission") + fun scanLeDevices( + onDeviceFound: (DeviceInfo) -> Unit, + durationMillis: Long = 10000 + ) { + if (!checkBluetoothScanPermission() || !checkLocationPermission()) { + return + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return + + val leScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner + val scanCallback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + val device = result.device + onDeviceFound( + DeviceInfo( + name = device.name, + address = device.address, + bondState = device.bondState, + bondStateText = getBondStateText(device.bondState), + deviceClass = device.bluetoothClass?.deviceClass, + deviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + device.type + } else null, + rssi = result.rssi, + uuids = result.scanRecord?.serviceUuids?.map { it.toString() } + ) + ) + } + } + + leScanner?.startScan(scanCallback) + + // 定时停止扫描 + android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ + leScanner?.stopScan(scanCallback) + }, durationMillis) + } + + /** + * 停止设备扫描 + */ + @SuppressLint("MissingPermission") + fun stopScan() { + if (checkBluetoothScanPermission()) { + bluetoothAdapter?.cancelDiscovery() + } + } + + // ==================== 蓝牙特性检测 ==================== + + /** + * 获取蓝牙特性信息 + */ + fun getBluetoothFeatures(): BluetoothFeatures { + val adapter = bluetoothAdapter + + return BluetoothFeatures( + version = getBluetoothVersion(), + bluetoothLe = isBluetoothLeSupported, + bluetooth = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH), + + // Bluetooth 4.x 特性 + le2MbPhy = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + adapter?.isLe2MPhySupported == true + } else false, + + leCodedPhy = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + adapter?.isLeCodedPhySupported == true + } else false, + + leExtendedAdvertising = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + adapter?.isLeExtendedAdvertisingSupported == true + } else false, + + lePeriodicAdvertising = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + adapter?.isLePeriodicAdvertisingSupported == true + } else false, + + // Bluetooth 5.x 特性 + bluetooth5Support = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + adapter?.isLe2MPhySupported == true || + adapter?.isLeCodedPhySupported == true || + adapter?.isLeExtendedAdvertisingSupported == true + } else false, + + leAudioSupport = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.hasSystemFeature("android.hardware.bluetooth.le.audio") + } else false, + + leAudioBroadcast = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.hasSystemFeature("android.hardware.bluetooth.le.audio.broadcast_source") + } else false, + + leAudioUnicast = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.hasSystemFeature("android.hardware.bluetooth.le.audio.unicast_client") + } else false, + + // 其他特性 + offloadedFiltering = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + adapter?.isOffloadedFilteringSupported == true + } else false, + + offloadedScanBatching = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + adapter?.isOffloadedScanBatchingSupported == true + } else false, + + multipleAdvertisement = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + adapter?.isMultipleAdvertisementSupported == true + } else false, + + lowEnergyExtended = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + adapter?.isLeExtendedAdvertisingSupported == true + } else false + ) + } + + /** + * 获取蓝牙版本(根据支持的特性推断) + */ + private fun getBluetoothVersion(): String { + val adapter = bluetoothAdapter ?: return "未知" + + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + context.packageManager.hasSystemFeature("android.hardware.bluetooth.le.audio") -> + "Bluetooth 5.2+" + + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + (adapter.isLe2MPhySupported || adapter.isLeCodedPhySupported) -> + "Bluetooth 5.0" + + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isBluetoothLeSupported -> + "Bluetooth 4.2" + + isBluetoothLeSupported -> + "Bluetooth 4.0" + + else -> + "Bluetooth 3.0 或更低" + } + } + + // ==================== 权限检查 ==================== + + /** + * 所需权限列表 + */ val requiredPermissions: Array get() = buildList { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { add(Manifest.permission.BLUETOOTH_CONNECT) + add(Manifest.permission.BLUETOOTH_SCAN) } else { add(Manifest.permission.BLUETOOTH) add(Manifest.permission.BLUETOOTH_ADMIN) } + add(Manifest.permission.ACCESS_FINE_LOCATION) }.toTypedArray() + + /** + * 检查蓝牙连接权限 + */ + private fun checkBluetoothConnectPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + ActivityCompat.checkSelfPermission( + context, + Manifest.permission.BLUETOOTH_CONNECT + ) == PackageManager.PERMISSION_GRANTED + } else { + ActivityCompat.checkSelfPermission( + context, + Manifest.permission.BLUETOOTH + ) == PackageManager.PERMISSION_GRANTED + } + } + + /** + * 检查蓝牙扫描权限 + */ + private fun checkBluetoothScanPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + ActivityCompat.checkSelfPermission( + context, + Manifest.permission.BLUETOOTH_SCAN + ) == PackageManager.PERMISSION_GRANTED + } else { + ActivityCompat.checkSelfPermission( + context, + Manifest.permission.BLUETOOTH_ADMIN + ) == PackageManager.PERMISSION_GRANTED + } + } + + /** + * 检查位置权限 + */ + private fun checkLocationPermission(): Boolean { + return ActivityCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + // ==================== 辅助方法 ==================== + + /** + * 获取配对状态文本 + */ + private fun getBondStateText(state: Int): String = when (state) { + BluetoothDevice.BOND_NONE -> "未配对" + BluetoothDevice.BOND_BONDING -> "正在配对" + BluetoothDevice.BOND_BONDED -> "已配对" + else -> "未知" + } + + /** + * 获取设备类型文本 + */ + fun getDeviceTypeText(type: Int?): String = when (type) { + BluetoothDevice.DEVICE_TYPE_CLASSIC -> "经典蓝牙" + BluetoothDevice.DEVICE_TYPE_LE -> "低功耗蓝牙" + BluetoothDevice.DEVICE_TYPE_DUAL -> "双模蓝牙" + else -> "未知" + } + + /** + * 获取文本格式的蓝牙信息摘要 + */ + fun text(): String { + val sb = StringBuilder() + + // 基本信息 + sb.append("=== 蓝牙基本信息 ===\n") + sb.append("适配器名称: ${adapterName ?: "未知"}\n") + sb.append("MAC地址: ${adapterMacAddress ?: "未知"}\n") + sb.append("状态: $stateText\n") + sb.append("是否启用: ${if (isEnabled) "是" else "否"}\n") + + // 蓝牙特性 + val features = getBluetoothFeatures() + sb.append("\n=== 蓝牙特性 ===\n") + sb.append("蓝牙版本: ${features.version}\n") + sb.append("经典蓝牙: ${if (features.bluetooth) "支持" else "不支持"}\n") + sb.append("低功耗蓝牙: ${if (features.bluetoothLe) "支持" else "不支持"}\n") + sb.append("Bluetooth 5: ${if (features.bluetooth5Support) "支持" else "不支持"}\n") + sb.append("LE Audio: ${if (features.leAudioSupport) "支持" else "不支持"}\n") + sb.append("2Mbps PHY: ${if (features.le2MbPhy) "支持" else "不支持"}\n") + sb.append("Coded PHY: ${if (features.leCodedPhy) "支持" else "不支持"}\n") + sb.append("扩展广播: ${if (features.leExtendedAdvertising) "支持" else "不支持"}\n") + sb.append("周期性广播: ${if (features.lePeriodicAdvertising) "支持" else "不支持"}\n") + + // 已配对设备 + val bonded = getBondedDevicesInfo() + sb.append("\n=== 已配对设备 (${bonded.size}) ===\n") + bonded.forEach { device -> + sb.append("设备: ${device.name ?: "未知"}\n") + sb.append(" 地址: ${device.address}\n") + sb.append(" 状态: ${device.bondStateText}\n") + device.deviceType?.let { + sb.append(" 类型: ${getDeviceTypeText(it)}\n") + } + } + + return sb.toString() + } } diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BuildInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BuildInfo.kt index a36881f..a3aa6fc 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BuildInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/BuildInfo.kt @@ -5,101 +5,257 @@ import android.os.Build // import androidx.security.state.SecurityStateManagerCompat import java.util.Date +/** + * 系统构建信息工具类 + * 提供 Android 系统构建版本、编译信息、安全补丁、JVM 信息等 + */ class BuildInfo(private val context: Context) { // private val securityStateManager = SecurityStateManagerCompat(context) // private val globalSecurityState = securityStateManager.getGlobalSecurityState() + // ==================== 构建基本信息 ==================== + + /** + * 系统构建指纹 + * 格式: brand/product/device:version/id/incremental:type/tags + * 示例: google/redfin/redfin:13/TP1A.220905.004/9012412:user/release-keys + */ val fingerprint: String get() = Build.FINGERPRINT + /** + * 构建标签 + * 通常为 "release-keys" (正式版) 或 "test-keys" (测试版) + */ val tags: String get() = Build.TAGS + /** + * 构建类型 + * 通常为 "user" (用户版)、"userdebug" (调试版) 或 "eng" (工程版) + */ val type: String get() = Build.TYPE + /** + * 系统构建日期 + * 返回构建时的时间戳 + */ val buildDate: Date get() = Date(Build.TIME) + /** + * 构建主机名 + * 编译系统的主机名 + */ val host: String get() = Build.HOST + /** + * 构建用户 + * 执行编译的用户名 + */ val user: String get() = Build.USER + /** + * 构建 ID + * 构建的唯一标识符,通常是版本号 + * 示例: TP1A.220905.004 + */ val id: String get() = Build.ID + /** + * 显示 ID + * 用于向用户显示的构建标识符 + */ val display: String get() = Build.DISPLAY + // ==================== Android 版本信息 ==================== + + /** + * Android 版本号 + * 示例: "13", "12", "11" 等 + */ val versionRelease: String get() = Build.VERSION.RELEASE + /** + * 版本代号 + * 开发阶段的代号,正式版通常为 "REL" + */ val versionCodename: String get() = Build.VERSION.CODENAME + /** + * SDK 版本号(API Level) + * 示例: 33 (Android 13), 31 (Android 12), 30 (Android 11) + */ val versionSdkInt: Int get() = Build.VERSION.SDK_INT + /** + * 预览版 SDK 版本号 + * 如果是预览版返回非 0 值,正式版返回 0 + */ val versionPreviewSdkInt: Int get() = Build.VERSION.PREVIEW_SDK_INT + /** + * 安全补丁级别 + * 格式: YYYY-MM-DD + * 示例: "2023-12-05" + */ val securityPatch: String get() = Build.VERSION.SECURITY_PATCH + /** + * 基础操作系统 + * 通常为空,除非是基于其他版本构建 + */ val baseOs: String get() = Build.VERSION.BASE_OS + /** + * 增量版本 + * 表示在同一版本号下的递增更新 + */ val incremental: String get() = Build.VERSION.INCREMENTAL + /** + * 版本号或代号 + * Android 11+ 可用 + * 如果是正式版返回版本号,预览版返回代号 + */ val releaseOrCodename: String? get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Build.VERSION.RELEASE_OR_CODENAME else null + /** + * 媒体性能等级 + * Android 12+ 可用 + * 返回设备的媒体性能等级,0 表示未定义 + * 示例: 31 (Android 12), 33 (Android 13) + */ val mediaPerformanceClass: Int get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Build.VERSION.MEDIA_PERFORMANCE_CLASS else 0 + /** + * 版本显示字符串 + * Android 13+ 可用 + * 用于显示的完整版本号(包括预览版) + */ val releaseOrPreviewDisplay: String? get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY else null + // ==================== JVM 信息 ==================== + + /** + * JVM 名称 + * 示例: "Dalvik" (Android) 或 "Java HotSpot(TM) 64-Bit Server VM" + */ val jvmName: String? get() = System.getProperty("java.vm.name") + /** + * JVM 供应商 + * 示例: "The Android Project" 或 "Oracle Corporation" + */ val jvmVendor: String? get() = System.getProperty("java.vm.vendor") + /** + * JVM 版本 + * 示例: "2.1.0" + */ val jvmVersion: String? get() = System.getProperty("java.vm.version") + /** + * Java 类版本 + * 示例: "50.0" (Java 6), "52.0" (Java 8) + */ val jvmClassVersion: String? get() = System.getProperty("java.class.version") + /** + * Java 规范名称 + * 示例: "Dalvik Core Library" + */ val jvmSpecificationName: String? get() = System.getProperty("java.specification.name") + /** + * Java 规范供应商 + * 示例: "The Android Project" + */ val jvmSpecificationVendor: String? get() = System.getProperty("java.specification.vendor") + /** + * Java 规范版本 + * 示例: "0.9" + */ val jvmSpecificationVersion: String? get() = System.getProperty("java.specification.version") + // ==================== 安全与内核信息 ==================== + + /** + * 厂商安全补丁级别 + * 需要 SecurityStateManagerCompat 支持 + * 某些设备可能有额外的厂商安全补丁 + */ val vendorSecurityPatchLevel: String? get() = null // globalSecurityState.getString(SecurityStateManagerCompat.KEY_VENDOR_SPL) + /** + * 内核版本 + * 需要 SecurityStateManagerCompat 支持 + * 示例: "5.10.157" + */ val kernelVersion: String? get() = null // globalSecurityState.getString(SecurityStateManagerCompat.KEY_KERNEL_VERSION) + /** + * 完整内核版本 + * 包含内核版本和编译信息 + * 示例: "5.10.157-android13-4-00001-gf0123456789a-ab9876543" + */ val kernelCompleteVersion: String? get() = System.getProperty("os.version") + /** + * Bootloader 版本 + * 设备的引导加载程序版本 + */ val bootloaderVersion: String get() = Build.BOOTLOADER + /** + * 基带版本(无线电版本) + * 移动网络基带固件版本 + */ val radioVersion: String? get() = Build.getRadioVersion() + // ==================== 分区信息 ==================== + + /** + * 指纹分区信息列表 + * Android 10+ 可用 + * 包含系统各分区的名称和指纹信息 + * + * 常见分区: + * - system: 系统分区 + * - vendor: 厂商分区 + * - product: 产品分区 + * - odm: ODM(原始设计制造商)分区 + * - system_ext: 系统扩展分区 + */ val fingerprintedPartitions: List get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Build.getFingerprintedPartitions().map { PartitionInfo(it.name, it.fingerprint) } @@ -107,5 +263,72 @@ class BuildInfo(private val context: Context) { emptyList() } + /** + * 分区信息数据类 + * @property name 分区名称 + * @property fingerprint 分区指纹(构建标识) + */ data class PartitionInfo(val name: String, val fingerprint: String) + + // ==================== 辅助方法 ==================== + + /** + * 获取文本格式的构建信息摘要 + * @return 格式化的文本信息 + */ + fun text(): String { + val sb = StringBuilder() + + // 基本构建信息 + sb.append("=== 系统构建信息 ===\n") + sb.append("指纹: $fingerprint\n") + sb.append("显示ID: $display\n") + sb.append("构建ID: $id\n") + sb.append("类型: $type\n") + sb.append("标签: $tags\n") + sb.append("构建日期: $buildDate\n") + sb.append("构建用户: $user\n") + sb.append("构建主机: $host\n") + + // Android 版本信息 + sb.append("\n=== Android 版本 ===\n") + sb.append("版本号: $versionRelease\n") + sb.append("SDK版本: $versionSdkInt\n") + sb.append("代号: $versionCodename\n") + sb.append("增量版本: $incremental\n") + sb.append("安全补丁: $securityPatch\n") + releaseOrCodename?.let { sb.append("版本/代号: $it\n") } + if (mediaPerformanceClass > 0) { + sb.append("媒体性能等级: $mediaPerformanceClass\n") + } + releaseOrPreviewDisplay?.let { sb.append("版本显示: $it\n") } + if (baseOs.isNotEmpty()) { + sb.append("基础OS: $baseOs\n") + } + + // JVM 信息 + sb.append("\n=== JVM 信息 ===\n") + jvmName?.let { sb.append("JVM名称: $it\n") } + jvmVendor?.let { sb.append("JVM供应商: $it\n") } + jvmVersion?.let { sb.append("JVM版本: $it\n") } + jvmClassVersion?.let { sb.append("类版本: $it\n") } + jvmSpecificationName?.let { sb.append("规范名称: $it\n") } + jvmSpecificationVersion?.let { sb.append("规范版本: $it\n") } + + // 硬件信息 + sb.append("\n=== 硬件信息 ===\n") + sb.append("Bootloader: $bootloaderVersion\n") + radioVersion?.let { sb.append("基带版本: $it\n") } + kernelCompleteVersion?.let { sb.append("内核版本: $it\n") } + + // 分区信息 + if (fingerprintedPartitions.isNotEmpty()) { + sb.append("\n=== 分区指纹 ===\n") + fingerprintedPartitions.forEach { partition -> + sb.append("${partition.name}: ${partition.fingerprint}\n") + } + } + + return sb.toString() + } } diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CameraInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CameraInfo.kt index ad59989..685dea8 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CameraInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CameraInfo.kt @@ -8,25 +8,111 @@ import android.hardware.camera2.CameraMetadata import android.os.Build import android.os.Handler import android.os.Looper +import android.util.Range +import android.util.SizeF import com.xyzshell.andinfo.R import com.xyzshell.andinfo.libs.camera.models.CameraCapability import com.xyzshell.andinfo.libs.camera.models.CameraFacing import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow +import kotlin.math.sqrt +/** + * 摄像头信息工具类 + * 提供摄像头硬件参数、特性、功能列表等详细信息 + * 需要权限: CAMERA + */ class CameraInfo(private val context: Context) { + /** + * 摄像头管理器 + */ private val cameraManager: CameraManager? = context.getSystemService( CameraManager::class.java ) + /** + * 摄像头详细信息数据类 + */ + data class CameraDetails( + val cameraId: String, // 摄像头ID + val facing: CameraFacing?, // 前置/后置/外置 + val facingText: String, // 方向文本 + + // 传感器信息 + val megaPixels: Double?, // 有效百万像素 + val resolution: String?, // 分辨率(宽x高) + val sensorSize: String?, // 传感器尺寸(mm) + val sensorSizeInch: Double?, // 传感器英寸 + val pixelSize: Double?, // 单像素尺寸(μm) + val colorFilterArrangement: String?, // 滤镜颜色排列(Bayer等) + + // 光学参数 + val focalLength: FloatArray?, // 焦距(mm) + val apertures: FloatArray?, // 光圈值 + val fieldOfView: String?, // 视野角度 + val focal35mmEquivalent: Double?, // 35mm等效焦距 + val cropFactor: Double?, // 裁切系数 + + // 曝光参数 + val isoRange: Range?, // ISO感光范围 + val exposureTimeRange: Range?, // 曝光时间范围(纳秒) + val shutterSpeedRange: String?, // 快门速度范围 + val exposureCompensationRange: String?, // 曝光补偿范围 + val exposureCompensationStep: Double?, // 曝光补偿步长 + + // 对焦参数 + val minimumFocusDistance: Float?, // 最小对焦距离(屈光度) + val minimumFocusDistanceCm: Double?, // 最小对焦距离(厘米) + val hyperFocalDistance: Float?, // 超焦距 + + // 功能特性 + val hasFlash: Boolean?, // 是否有闪光灯 + val hasOpticalStabilization: Boolean?, // 光学防抖(OIS) + val hasVideoStabilization: Boolean?, // 视频防抖 + val hasAutoFocus: Boolean?, // 自动对焦 + val camera2ApiSupport: String?, // Camera2 API支持级别 + + // 面部检测 + val maxFaceCount: Int?, // 最大面部检测数量 + val faceDetectionModes: List?, // 面部检测模式列表 + + // 功能列表 + val capabilities: List, // 摄像头能力 + val supportedFeatures: List, // 支持的功能(HDR、RAW等) + + // 模式列表 + val autoExposureModes: List?, // 自动曝光模式 + val autoFocusModes: List?, // 自动对焦模式 + val whiteBalanceModes: List?, // 白平衡模式 + val sceneModes: List?, // 场景模式 + val colorEffects: List?, // 色彩效果 + + // 视频能力 + val maxFrameRate: Int?, // 最大帧率 + val supportsHighSpeedVideo: Boolean, // 是否支持高速视频 + val supports4K: Boolean, // 是否支持4K录制 + val supports8K: Boolean // 是否支持8K录制 + ) + + /** + * 获取所有摄像头ID列表 + */ val cameraIds: List get() = cameraManager?.cameraIdList?.toList() ?: emptyList() + /** + * 获取指定摄像头的特性对象 + * @param cameraId 摄像头ID + * @return CameraCharacteristics 对象 + */ fun getCameraCharacteristics(cameraId: String): CameraCharacteristics? { return cameraManager?.getCameraCharacteristics(cameraId) } + /** + * 监听摄像头可用性变化的 Flow + */ fun cameraIdsFlow() = cameraManager?.let { manager -> callbackFlow { val onCameraIdUpdated = { @@ -55,6 +141,12 @@ class CameraInfo(private val context: Context) { } } + // ==================== 基本信息 ==================== + + /** + * 获取摄像头朝向 + * @return 前置/后置/外置 + */ fun getCameraFacing(characteristics: CameraCharacteristics): CameraFacing? { return characteristics.get(CameraCharacteristics.LENS_FACING)?.let { when (it) { @@ -66,22 +158,291 @@ class CameraInfo(private val context: Context) { } } + /** + * 获取摄像头朝向文本 + */ + fun getCameraFacingText(characteristics: CameraCharacteristics): String { + return when (getCameraFacing(characteristics)) { + CameraFacing.BACK -> "后置" + CameraFacing.FRONT -> "前置" + CameraFacing.EXTERNAL -> "外置" + else -> "未知" + } + } + + // ==================== 传感器信息 ==================== + + /** + * 获取像素阵列尺寸(分辨率) + * @return 格式: "宽x高" + */ fun getPixelArraySize(characteristics: CameraCharacteristics): String? { return characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)?.toString() } + /** + * 获取有效百万像素 + */ + fun getMegaPixels(characteristics: CameraCharacteristics): Double? { + val size = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE) + return size?.let { (it.width * it.height) / 1_000_000.0 } + } + + /** + * 获取传感器物理尺寸 + * @return 格式: "宽x高 mm" + */ fun getSensorPhysicalSize(characteristics: CameraCharacteristics): String? { - return characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)?.toString() + return characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)?.let { + String.format("%.2f x %.2f mm", it.width, it.height) + } } - fun hasFlashUnit(characteristics: CameraCharacteristics): Boolean? { - return characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) + /** + * 获取传感器尺寸(英寸,对角线) + */ + fun getSensorSizeInch(characteristics: CameraCharacteristics): Double? { + return characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)?.let { + val diagonal = sqrt((it.width * it.width + it.height * it.height).toDouble()) + diagonal / 25.4 // 转换为英寸 + } } + /** + * 获取单像素尺寸(微米) + */ + fun getPixelSize(characteristics: CameraCharacteristics): Double? { + val physicalSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE) + val pixelArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE) + + return if (physicalSize != null && pixelArray != null) { + val pixelWidth = (physicalSize.width * 1000) / pixelArray.width + val pixelHeight = (physicalSize.height * 1000) / pixelArray.height + (pixelWidth + pixelHeight) / 2.0 + } else null + } + + /** + * 获取滤镜颜色排列 + */ + fun getColorFilterArrangement(characteristics: CameraCharacteristics): String? { + return characteristics.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT)?.let { + when (it) { + CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB -> "RGGB (Bayer)" + CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG -> "GRBG (Bayer)" + CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG -> "GBRG (Bayer)" + CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR -> "BGGR (Bayer)" + CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB -> "RGB" + 5 -> "Mono/NIR (单色/近红外)" // MONO/NIR + else -> "未知" + } + } + } + + // ==================== 光学参数 ==================== + + /** + * 获取焦距数组 + */ + fun getFocalLength(characteristics: CameraCharacteristics): FloatArray? { + return characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) + } + + /** + * 获取光圈值数组 + */ fun getAvailableApertures(characteristics: CameraCharacteristics): FloatArray? { return characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES) } + /** + * 获取35mm等效焦距 + */ + fun getFocal35mmEquivalent(characteristics: CameraCharacteristics): Double? { + val focalLength = getFocalLength(characteristics)?.firstOrNull() + val cropFactor = getCropFactor(characteristics) + return if (focalLength != null && cropFactor != null) { + focalLength * cropFactor + } else null + } + + /** + * 获取裁切系数(相对于全画幅) + */ + fun getCropFactor(characteristics: CameraCharacteristics): Double? { + val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE) + return sensorSize?.let { + val diagonal = sqrt((it.width * it.width + it.height * it.height).toDouble()) + 43.27 / diagonal // 全画幅对角线约43.27mm + } + } + + /** + * 获取视野角度 + */ + fun getFieldOfView(characteristics: CameraCharacteristics): String? { + val focalLength = getFocalLength(characteristics)?.firstOrNull() + val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE) + + return if (focalLength != null && sensorSize != null) { + val hFov = 2 * Math.toDegrees(Math.atan((sensorSize.width / 2.0 / focalLength).toDouble())) + val vFov = 2 * Math.toDegrees(Math.atan((sensorSize.height / 2.0 / focalLength).toDouble())) + String.format("H: %.1f°, V: %.1f°", hFov, vFov) + } else null + } + + // ==================== 曝光参数 ==================== + + /** + * 获取ISO感光度范围 + */ + fun getIsoRange(characteristics: CameraCharacteristics): Range? { + return characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) + } + + /** + * 获取曝光时间范围(纳秒) + */ + fun getExposureTimeRange(characteristics: CameraCharacteristics): Range? { + return characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) + } + + /** + * 获取快门速度范围(格式化字符串) + */ + fun getShutterSpeedRange(characteristics: CameraCharacteristics): String? { + val exposureRange = getExposureTimeRange(characteristics) + return exposureRange?.let { + val minSeconds = it.lower / 1_000_000_000.0 + val maxSeconds = it.upper / 1_000_000_000.0 + String.format("1/%.0f - %.2fs", 1.0 / minSeconds, maxSeconds) + } + } + + /** + * 获取曝光补偿范围 + */ + fun getExposureCompensationRange(characteristics: CameraCharacteristics): String? { + val range = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE) + val step = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP) + + return if (range != null && step != null) { + val minEv = range.lower * step.toDouble() + val maxEv = range.upper * step.toDouble() + String.format("%.1f ~ %.1f EV", minEv, maxEv) + } else null + } + + /** + * 获取曝光补偿步长 + */ + fun getExposureCompensationStep(characteristics: CameraCharacteristics): Double? { + return characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)?.toDouble() + } + + // ==================== 对焦参数 ==================== + + /** + * 获取最小对焦距离(屈光度) + */ + fun getMinimumFocusDistance(characteristics: CameraCharacteristics): Float? { + return characteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE) + } + + /** + * 获取最小对焦距离(厘米) + */ + fun getMinimumFocusDistanceCm(characteristics: CameraCharacteristics): Double? { + val diopters = getMinimumFocusDistance(characteristics) + return diopters?.let { + if (it > 0) 100.0 / it else null + } + } + + /** + * 获取超焦距 + */ + fun getHyperFocalDistance(characteristics: CameraCharacteristics): Float? { + return characteristics.get(CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE) + } + + // ==================== 功能特性 ==================== + + /** + * 是否有闪光灯 + */ + fun hasFlashUnit(characteristics: CameraCharacteristics): Boolean? { + return characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) + } + + /** + * 是否支持光学防抖(OIS) + */ + fun hasOpticalStabilization(characteristics: CameraCharacteristics): Boolean { + val modes = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION) + return modes?.contains(CameraCharacteristics.LENS_OPTICAL_STABILIZATION_MODE_ON) == true + } + + /** + * 是否支持视频防抖 + */ + fun hasVideoStabilization(characteristics: CameraCharacteristics): Boolean { + val modes = characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) + return modes?.contains(CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON) == true + } + + /** + * 是否支持自动对焦 + */ + fun hasAutoFocus(characteristics: CameraCharacteristics): Boolean { + val minDistance = getMinimumFocusDistance(characteristics) + return minDistance != null && minDistance > 0 + } + + /** + * 获取Camera2 API支持级别 + */ + fun getCamera2ApiSupport(characteristics: CameraCharacteristics): String? { + return characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)?.let { + when (it) { + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED -> "LIMITED" + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL -> "FULL" + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY -> "LEGACY" + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 -> "LEVEL_3" + CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) "EXTERNAL" else "未知" + else -> "未知" + } + } + } + + // ==================== 面部检测 ==================== + + /** + * 获取最大面部检测数量 + */ + fun getMaxFaceCount(characteristics: CameraCharacteristics): Int? { + return characteristics.get(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT) + } + + /** + * 获取面部检测模式列表 + */ + fun getFaceDetectionModes(characteristics: CameraCharacteristics): List? { + return characteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES)?.map { + when (it) { + CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_OFF -> "关闭" + CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE -> "简单" + CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_FULL -> "完整" + else -> "未知" + } + } + } + + // ==================== 能力列表 ==================== + + /** + * 获取摄像头能力列表 + */ fun getAvailableCapabilities(characteristics: CameraCharacteristics): List { val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) return capabilities?.toList()?.mapNotNull { capability -> @@ -111,4 +472,235 @@ class CameraInfo(private val context: Context) { } } ?: emptyList() } + + /** + * 获取支持的功能列表(人类可读) + */ + fun getSupportedFeatures(characteristics: CameraCharacteristics): List { + val features = mutableListOf() + val capabilities = getAvailableCapabilities(characteristics) + + if (capabilities.contains(CameraCapability.RAW)) features.add("RAW拍摄") + if (capabilities.contains(CameraCapability.BURST_CAPTURE)) features.add("连拍") + if (capabilities.contains(CameraCapability.DEPTH_OUTPUT)) features.add("景深输出") + if (capabilities.contains(CameraCapability.CONSTRAINED_HIGH_SPEED_VIDEO)) features.add("高速视频") + if (capabilities.contains(CameraCapability.MANUAL_SENSOR)) features.add("手动模式") + if (capabilities.contains(CameraCapability.MONOCHROME)) features.add("黑白模式") + if (capabilities.contains(CameraCapability.ULTRA_HIGH_RESOLUTION_SENSOR)) features.add("超高分辨率") + if (capabilities.contains(CameraCapability.DYNAMIC_RANGE_TEN_BIT)) features.add("10-bit HDR") + + if (hasOpticalStabilization(characteristics)) features.add("光学防抖") + if (hasVideoStabilization(characteristics)) features.add("视频防抖") + + return features + } + + // ==================== 模式列表 ==================== + + /** + * 获取自动曝光模式列表 + */ + fun getAutoExposureModes(characteristics: CameraCharacteristics): List? { + return characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)?.map { + when (it) { + CameraCharacteristics.CONTROL_AE_MODE_OFF -> "关闭" + CameraCharacteristics.CONTROL_AE_MODE_ON -> "自动" + CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH -> "自动闪光" + CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH -> "强制闪光" + CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE -> "防红眼" + CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) "外置闪光" else "未知" + else -> "未知" + } + } + } + + /** + * 获取自动对焦模式列表 + */ + fun getAutoFocusModes(characteristics: CameraCharacteristics): List? { + return characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)?.map { + when (it) { + CameraCharacteristics.CONTROL_AF_MODE_OFF -> "关闭" + CameraCharacteristics.CONTROL_AF_MODE_AUTO -> "自动" + CameraCharacteristics.CONTROL_AF_MODE_MACRO -> "微距" + CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_VIDEO -> "连续视频" + CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_PICTURE -> "连续拍照" + CameraCharacteristics.CONTROL_AF_MODE_EDOF -> "EDOF" + else -> "未知" + } + } + } + + /** + * 获取白平衡模式列表 + */ + fun getWhiteBalanceModes(characteristics: CameraCharacteristics): List? { + return characteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES)?.map { + when (it) { + CameraCharacteristics.CONTROL_AWB_MODE_OFF -> "关闭" + CameraCharacteristics.CONTROL_AWB_MODE_AUTO -> "自动" + CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT -> "白炽灯" + CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT -> "荧光灯" + CameraCharacteristics.CONTROL_AWB_MODE_WARM_FLUORESCENT -> "暖荧光" + CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT -> "日光" + CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT -> "阴天" + CameraCharacteristics.CONTROL_AWB_MODE_TWILIGHT -> "黄昏" + CameraCharacteristics.CONTROL_AWB_MODE_SHADE -> "阴影" + else -> "未知" + } + } + } + + /** + * 获取场景模式列表 + */ + fun getSceneModes(characteristics: CameraCharacteristics): List? { + return characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES)?.map { + when (it) { + CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED -> "禁用" + CameraCharacteristics.CONTROL_SCENE_MODE_FACE_PRIORITY -> "人像优先" + CameraCharacteristics.CONTROL_SCENE_MODE_ACTION -> "运动" + CameraCharacteristics.CONTROL_SCENE_MODE_PORTRAIT -> "人像" + CameraCharacteristics.CONTROL_SCENE_MODE_LANDSCAPE -> "风景" + CameraCharacteristics.CONTROL_SCENE_MODE_NIGHT -> "夜景" + CameraCharacteristics.CONTROL_SCENE_MODE_NIGHT_PORTRAIT -> "夜景人像" + CameraCharacteristics.CONTROL_SCENE_MODE_THEATRE -> "剧院" + CameraCharacteristics.CONTROL_SCENE_MODE_BEACH -> "海滩" + CameraCharacteristics.CONTROL_SCENE_MODE_SNOW -> "雪景" + CameraCharacteristics.CONTROL_SCENE_MODE_SUNSET -> "日落" + CameraCharacteristics.CONTROL_SCENE_MODE_STEADYPHOTO -> "防抖" + CameraCharacteristics.CONTROL_SCENE_MODE_FIREWORKS -> "烟火" + CameraCharacteristics.CONTROL_SCENE_MODE_SPORTS -> "体育" + CameraCharacteristics.CONTROL_SCENE_MODE_PARTY -> "派对" + CameraCharacteristics.CONTROL_SCENE_MODE_CANDLELIGHT -> "烛光" + CameraCharacteristics.CONTROL_SCENE_MODE_BARCODE -> "条形码" + CameraCharacteristics.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) "高速视频" else "未知" + CameraCharacteristics.CONTROL_SCENE_MODE_HDR -> "HDR" + else -> "未知" + } + } + } + + /** + * 获取色彩效果列表 + */ + fun getColorEffects(characteristics: CameraCharacteristics): List? { + return characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS)?.map { + when (it) { + CameraCharacteristics.CONTROL_EFFECT_MODE_OFF -> "关闭" + CameraCharacteristics.CONTROL_EFFECT_MODE_MONO -> "黑白" + CameraCharacteristics.CONTROL_EFFECT_MODE_NEGATIVE -> "负片" + CameraCharacteristics.CONTROL_EFFECT_MODE_SOLARIZE -> "曝光" + CameraCharacteristics.CONTROL_EFFECT_MODE_SEPIA -> "棕褐色" + CameraCharacteristics.CONTROL_EFFECT_MODE_POSTERIZE -> "海报化" + CameraCharacteristics.CONTROL_EFFECT_MODE_WHITEBOARD -> "白板" + CameraCharacteristics.CONTROL_EFFECT_MODE_BLACKBOARD -> "黑板" + CameraCharacteristics.CONTROL_EFFECT_MODE_AQUA -> "水蓝" + else -> "未知" + } + } + } + + // ==================== 视频能力 ==================== + + /** + * 获取最大帧率 + */ + fun getMaxFrameRate(characteristics: CameraCharacteristics): Int? { + val ranges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES) + return ranges?.maxOfOrNull { it.upper } + } + + /** + * 是否支持高速视频 + */ + fun supportsHighSpeedVideo(characteristics: CameraCharacteristics): Boolean { + return getAvailableCapabilities(characteristics) + .contains(CameraCapability.CONSTRAINED_HIGH_SPEED_VIDEO) + } + + /** + * 是否支持4K录制 + */ + fun supports4K(characteristics: CameraCharacteristics): Boolean { + // 简化判断,实际需要检查 StreamConfigurationMap + val resolution = getPixelArraySize(characteristics) + return resolution?.contains("3840") == true || resolution?.contains("4096") == true + } + + /** + * 是否支持8K录制 + */ + fun supports8K(characteristics: CameraCharacteristics): Boolean { + val resolution = getPixelArraySize(characteristics) + return resolution?.contains("7680") == true + } + + // ==================== 综合信息 ==================== + + /** + * 获取摄像头详细信息 + */ + fun getCameraDetails(cameraId: String): CameraDetails? { + val characteristics = getCameraCharacteristics(cameraId) ?: return null + + return CameraDetails( + cameraId = cameraId, + facing = getCameraFacing(characteristics), + facingText = getCameraFacingText(characteristics), + + megaPixels = getMegaPixels(characteristics), + resolution = getPixelArraySize(characteristics), + sensorSize = getSensorPhysicalSize(characteristics), + sensorSizeInch = getSensorSizeInch(characteristics), + pixelSize = getPixelSize(characteristics), + colorFilterArrangement = getColorFilterArrangement(characteristics), + + focalLength = getFocalLength(characteristics), + apertures = getAvailableApertures(characteristics), + fieldOfView = getFieldOfView(characteristics), + focal35mmEquivalent = getFocal35mmEquivalent(characteristics), + cropFactor = getCropFactor(characteristics), + + isoRange = getIsoRange(characteristics), + exposureTimeRange = getExposureTimeRange(characteristics), + shutterSpeedRange = getShutterSpeedRange(characteristics), + exposureCompensationRange = getExposureCompensationRange(characteristics), + exposureCompensationStep = getExposureCompensationStep(characteristics), + + minimumFocusDistance = getMinimumFocusDistance(characteristics), + minimumFocusDistanceCm = getMinimumFocusDistanceCm(characteristics), + hyperFocalDistance = getHyperFocalDistance(characteristics), + + hasFlash = hasFlashUnit(characteristics), + hasOpticalStabilization = hasOpticalStabilization(characteristics), + hasVideoStabilization = hasVideoStabilization(characteristics), + hasAutoFocus = hasAutoFocus(characteristics), + camera2ApiSupport = getCamera2ApiSupport(characteristics), + + maxFaceCount = getMaxFaceCount(characteristics), + faceDetectionModes = getFaceDetectionModes(characteristics), + + capabilities = getAvailableCapabilities(characteristics), + supportedFeatures = getSupportedFeatures(characteristics), + + autoExposureModes = getAutoExposureModes(characteristics), + autoFocusModes = getAutoFocusModes(characteristics), + whiteBalanceModes = getWhiteBalanceModes(characteristics), + sceneModes = getSceneModes(characteristics), + colorEffects = getColorEffects(characteristics), + + maxFrameRate = getMaxFrameRate(characteristics), + supportsHighSpeedVideo = supportsHighSpeedVideo(characteristics), + supports4K = supports4K(characteristics), + supports8K = supports8K(characteristics) + ) + } + + /** + * 获取所有摄像头的详细信息列表 + */ + fun getAllCameraDetails(): List { + return cameraIds.mapNotNull { getCameraDetails(it) } + } } diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CpuInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CpuInfo.kt index d2a767b..3204cc4 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CpuInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/CpuInfo.kt @@ -1,5 +1,6 @@ package com.xyzshell.andinfo.libs +import android.os.Build import com.xyzshell.andinfo.libs.cpu.models.Cache import com.xyzshell.andinfo.libs.cpu.models.Cluster import com.xyzshell.andinfo.libs.cpu.models.Core @@ -7,60 +8,640 @@ import com.xyzshell.andinfo.libs.cpu.models.Package import com.xyzshell.andinfo.libs.cpu.models.Processor import com.xyzshell.andinfo.libs.cpu.models.UarchInfo import com.xyzshell.andinfo.libs.cpu.utils.CpuInfoUtils +import java.io.File +/** + * CPU 信息类 + * 用于获取设备处理器的详细信息,包括核心数、频率、架构、缓存等 + */ class CpuInfo { + + // ==================== 基础处理器信息 ==================== + + /** + * 获取所有逻辑处理器列表 + * @return 逻辑处理器列表 + */ val processors: List get() = CpuInfoUtils.getProcessors() + /** + * 获取所有物理核心列表 + * @return 物理核心列表 + */ val cores: List get() = CpuInfoUtils.getCores() + /** + * 获取所有核心集群列表(大小核分组) + * @return 集群列表 + */ val clusters: List get() = CpuInfoUtils.getClusters() + /** + * 获取所有物理封装(芯片)列表 + * @return 封装列表 + */ val packages: List get() = CpuInfoUtils.getPackages() + /** + * 获取所有微架构信息列表 + * @return 微架构信息列表 + */ val uarchs: List get() = CpuInfoUtils.getUarchs() + // ==================== 缓存信息 ==================== + + /** + * L1 指令缓存列表 + * @return L1i 缓存列表 + */ val l1iCaches: List get() = CpuInfoUtils.getL1iCaches() + /** + * L1 数据缓存列表 + * @return L1d 缓存列表 + */ val l1dCaches: List get() = CpuInfoUtils.getL1dCaches() + /** + * L2 缓存列表 + * @return L2 缓存列表 + */ val l2Caches: List get() = CpuInfoUtils.getL2Caches() + /** + * L3 缓存列表 + * @return L3 缓存列表 + */ val l3Caches: List get() = CpuInfoUtils.getL3Caches() + /** + * L4 缓存列表 + * @return L4 缓存列表 + */ val l4Caches: List get() = CpuInfoUtils.getL4Caches() - fun hardware(): String { - return if (processors.isNotEmpty()) { - processors[0].cpuPackage?.name ?: "未知" + // ==================== 处理器名称和供应商 ==================== + + /** + * 获取处理器名称(SoC 型号) + * @return 处理器名称,例如 "Qualcomm Snapdragon 888" + */ + fun getProcessorName(): String { + return if (packages.isNotEmpty()) { + packages[0].name } else { "未知" } } + /** + * 获取处理器供应商 + * @return 供应商名称,例如 "Qualcomm", "ARM", "Intel" 等 + */ + fun getVendor(): String { + return if (cores.isNotEmpty()) { + cores[0].vendor.name + } else { + "未知" + } + } + + // ==================== 核心数量信息 ==================== + + /** + * 获取物理核心总数 + * @return 核心数量 + */ + fun getCoreCount(): Int { + return cores.size + } + + /** + * 获取逻辑处理器总数 + * @return 逻辑处理器数量 + */ + fun getProcessorCount(): Int { + return processors.size + } + + // ==================== 大小核信息 ==================== + + /** + * 大小核信息数据类 + * @property bigCoreCount 大核数量 + * @property midCoreCount 中核数量 + * @property littleCoreCount 小核数量 + * @property bigCoreFreq 大核最高频率(Hz) + * @property midCoreFreq 中核最高频率(Hz) + * @property littleCoreFreq 小核最高频率(Hz) + */ + data class ClusterInfo( + val bigCoreCount: Int, + val midCoreCount: Int, + val littleCoreCount: Int, + val bigCoreFreq: ULong, + val midCoreFreq: ULong, + val littleCoreFreq: ULong + ) + + /** + * 获取大小核信息 + * @return 大小核信息对象 + */ + fun getClusterInfo(): ClusterInfo { + if (clusters.isEmpty()) { + return ClusterInfo(0, 0, 0, 0UL, 0UL, 0UL) + } + + // 按频率排序集群 + val sortedClusters = clusters.sortedByDescending { cluster -> + cores.filter { it.cluster.clusterId == cluster.clusterId } + .maxOfOrNull { it.frequency } ?: 0UL + } + + return when (sortedClusters.size) { + 1 -> ClusterInfo( + 0, 0, sortedClusters[0].coreCount.toInt(), + 0UL, 0UL, + cores.filter { it.cluster.clusterId == sortedClusters[0].clusterId } + .maxOfOrNull { it.frequency } ?: 0UL + ) + 2 -> ClusterInfo( + sortedClusters[0].coreCount.toInt(), + 0, + sortedClusters[1].coreCount.toInt(), + cores.filter { it.cluster.clusterId == sortedClusters[0].clusterId } + .maxOfOrNull { it.frequency } ?: 0UL, + 0UL, + cores.filter { it.cluster.clusterId == sortedClusters[1].clusterId } + .maxOfOrNull { it.frequency } ?: 0UL + ) + else -> ClusterInfo( + sortedClusters[0].coreCount.toInt(), + sortedClusters[1].coreCount.toInt(), + sortedClusters[2].coreCount.toInt(), + cores.filter { it.cluster.clusterId == sortedClusters[0].clusterId } + .maxOfOrNull { it.frequency } ?: 0UL, + cores.filter { it.cluster.clusterId == sortedClusters[1].clusterId } + .maxOfOrNull { it.frequency } ?: 0UL, + cores.filter { it.cluster.clusterId == sortedClusters[2].clusterId } + .maxOfOrNull { it.frequency } ?: 0UL + ) + } + } + + // ==================== 制程和架构 ==================== + + /** + * 制程信息数据类 + * @property process 制程工艺,例如 "4nm", "5nm", "7nm" 等 + * @property foundry 代工厂,例如 "台积电", "三星" 等 + * @property node 详细制程节点,例如 "TSMC N4P", "Samsung 4LPE" 等 + */ + data class ProcessInfo( + val process: String, + val foundry: String, + val node: String = "" + ) + + /** + * 获取制程信息(根据 SoC 型号查询) + * @return 制程信息对象,包含制程工艺和代工厂 + */ + fun getProcessInfo(): ProcessInfo { + val processorName = getProcessorName().lowercase() + + return when { + // 高通 Snapdragon 8 系列 + "snapdragon 8 gen 3" in processorName || "sm8650" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4P") + "snapdragon 8 gen 2" in processorName || "sm8550" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "snapdragon 8 gen 1" in processorName || "sm8450" in processorName -> + ProcessInfo("4nm", "三星", "Samsung 4LPE") + "snapdragon 888+" in processorName || "sm8350-ab" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "snapdragon 888" in processorName || "sm8350" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "snapdragon 870" in processorName || "sm8250-ac" in processorName -> + ProcessInfo("7nm", "台积电", "TSMC N7P") + "snapdragon 865+" in processorName || "sm8250-ab" in processorName -> + ProcessInfo("7nm", "台积电", "TSMC N7P") + "snapdragon 865" in processorName || "sm8250" in processorName -> + ProcessInfo("7nm", "台积电", "TSMC N7P") + + // 高通 Snapdragon 7 系列 + "snapdragon 7+ gen 2" in processorName || "sm7475" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "snapdragon 778g" in processorName || "sm7325" in processorName -> + ProcessInfo("6nm", "台积电", "TSMC N6") + + // 高通 Snapdragon 6 系列 + "snapdragon 695" in processorName || "sm6375" in processorName -> + ProcessInfo("6nm", "台积电", "TSMC N6") + + // 联发科 Dimensity 9000 系列 + "dimensity 9300" in processorName || "mt6989" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4P") + "dimensity 9200+" in processorName || "mt6985" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "dimensity 9200" in processorName || "mt6983" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "dimensity 9000+" in processorName || "mt6985" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "dimensity 9000" in processorName || "mt6983" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + + // 联发科 Dimensity 8000 系列 + "dimensity 8300" in processorName || "mt6897" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "dimensity 8200" in processorName || "mt6896" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "dimensity 8100" in processorName || "mt6895" in processorName -> + ProcessInfo("5nm", "台积电", "TSMC N5") + + // 联发科 Dimensity 1000 系列 + "dimensity 1200" in processorName || "mt6893" in processorName -> + ProcessInfo("6nm", "台积电", "TSMC N6") + "dimensity 1080" in processorName || "mt6877" in processorName -> + ProcessInfo("6nm", "台积电", "TSMC N6") + + // 联发科 Helio 系列 + "helio g99" in processorName || "mt6789" in processorName -> + ProcessInfo("6nm", "台积电", "TSMC N6") + + // 三星 Exynos + "exynos 2400" in processorName || "s5e9945" in processorName -> + ProcessInfo("4nm", "三星", "Samsung 4LPP+") + "exynos 2200" in processorName || "s5e9925" in processorName -> + ProcessInfo("4nm", "三星", "Samsung 4LPE") + "exynos 2100" in processorName || "s5e9840" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "exynos 1080" in processorName || "s5e1080" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "exynos 1380" in processorName || "s5e8835" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "exynos 1280" in processorName || "s5e8825" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + + // 谷歌 Tensor + "tensor g3" in processorName || "gs301" in processorName -> + ProcessInfo("4nm", "三星", "Samsung 4LPP+") + "tensor g2" in processorName || "gs201" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "tensor g1" in processorName || "gs101" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + "tensor" in processorName -> + ProcessInfo("5nm", "三星", "Samsung 5LPE") + + // 华为麒麟 + "kirin 9000" in processorName -> + ProcessInfo("5nm", "台积电", "TSMC N5") + "kirin 9000e" in processorName -> + ProcessInfo("5nm", "台积电", "TSMC N5") + "kirin 990" in processorName -> + ProcessInfo("7nm", "台积电", "TSMC N7+") + "kirin 980" in processorName -> + ProcessInfo("7nm", "台积电", "TSMC N7") + "kirin 810" in processorName -> + ProcessInfo("7nm", "台积电", "TSMC N7") + + // 苹果 A 系列(参考) + "apple a17" in processorName -> + ProcessInfo("3nm", "台积电", "TSMC N3B") + "apple a16" in processorName -> + ProcessInfo("4nm", "台积电", "TSMC N4") + "apple a15" in processorName -> + ProcessInfo("5nm", "台积电", "TSMC N5P") + "apple a14" in processorName -> + ProcessInfo("5nm", "台积电", "TSMC N5") + + // 默认情况 + else -> ProcessInfo("未知", "未知", "") + } + } + + /** + * 获取制程信息(字符串格式,兼容旧版本) + * @return 制程信息,例如 "5nm", "7nm" 等 + */ + fun getProcess(): String { + return getProcessInfo().process + } + + /** + * 获取代工厂信息 + * @return 代工厂名称,例如 "台积电", "三星" 等 + */ + fun getFoundry(): String { + return getProcessInfo().foundry + } + + /** + * 获取详细制程节点 + * @return 制程节点,例如 "TSMC N4P", "Samsung 5LPE" 等 + */ + fun getProcessNode(): String { + return getProcessInfo().node.ifEmpty { getProcessInfo().process } + } + + /** + * 获取 CPU 架构 + * @return 架构名称,例如 "Cortex-A78", "Cortex-X1" 等 + */ + fun getArchitecture(): String { + return if (cores.isNotEmpty()) { + cores[0].uarch.name + } else { + "未知" + } + } + + /** + * 获取所有核心的架构列表(去重) + * @return 架构列表 + */ + fun getAllArchitectures(): List { + return cores.map { it.uarch.name }.distinct() + } + + // ==================== ABI 信息 ==================== + + /** + * 获取当前设备的主 ABI + * @return ABI 字符串,例如 "arm64-v8a", "armeabi-v7a" 等 + */ + fun getAbi(): String { + return Build.SUPPORTED_ABIS.firstOrNull() ?: "未知" + } + + /** + * 获取所有支持的 ABI 列表 + * @return ABI 列表 + */ + fun getSupportedAbis(): List { + return Build.SUPPORTED_ABIS.toList() + } + + // ==================== 频率信息 ==================== + + /** + * 单个核心的频率信息 + * @property coreId 核心 ID + * @property currentFreq 当前频率(KHz) + * @property minFreq 最小频率(KHz) + * @property maxFreq 最大频率(KHz) + * @property availableFreqs 可用频率列表(KHz) + */ + data class CoreFrequencyInfo( + val coreId: Int, + val currentFreq: Long, + val minFreq: Long, + val maxFreq: Long, + val availableFreqs: List + ) + + /** + * 获取所有核心的详细频率信息 + * @return 核心频率信息列表 + */ + fun getCoreFrequencies(): List { + val result = mutableListOf() + + for (i in 0 until getCoreCount()) { + val currentFreq = readFrequency("/sys/devices/system/cpu/cpu$i/cpufreq/scaling_cur_freq") + val minFreq = readFrequency("/sys/devices/system/cpu/cpu$i/cpufreq/cpuinfo_min_freq") + val maxFreq = readFrequency("/sys/devices/system/cpu/cpu$i/cpufreq/cpuinfo_max_freq") + val availableFreqs = readAvailableFrequencies("/sys/devices/system/cpu/cpu$i/cpufreq/scaling_available_frequencies") + + result.add(CoreFrequencyInfo(i, currentFreq, minFreq, maxFreq, availableFreqs)) + } + + return result + } + + /** + * 读取频率值 + * @param path 频率文件路径 + * @return 频率值(KHz),失败返回 0 + */ + private fun readFrequency(path: String): Long { + return try { + File(path).readText().trim().toLongOrNull() ?: 0L + } catch (e: Exception) { + 0L + } + } + + /** + * 读取可用频率列表 + * @param path 可用频率文件路径 + * @return 频率列表(KHz) + */ + private fun readAvailableFrequencies(path: String): List { + return try { + File(path).readText().trim() + .split("\\s+".toRegex()) + .mapNotNull { it.toLongOrNull() } + } catch (e: Exception) { + emptyList() + } + } + + // ==================== 调频器信息 ==================== + + /** + * 获取指定核心的调频器 + * @param coreId 核心 ID + * @return 调频器名称,例如 "schedutil", "interactive" 等 + */ + fun getGovernor(coreId: Int): String { + return try { + File("/sys/devices/system/cpu/cpu$coreId/cpufreq/scaling_governor") + .readText().trim() + } catch (e: Exception) { + "未知" + } + } + + /** + * 获取所有核心的调频器 + * @return 调频器列表 + */ + fun getAllGovernors(): List { + return (0 until getCoreCount()).map { getGovernor(it) } + } + + /** + * 获取可用的调频器列表 + * @param coreId 核心 ID + * @return 可用调频器列表 + */ + fun getAvailableGovernors(coreId: Int = 0): List { + return try { + File("/sys/devices/system/cpu/cpu$coreId/cpufreq/scaling_available_governors") + .readText().trim() + .split("\\s+".toRegex()) + } catch (e: Exception) { + emptyList() + } + } + + // ==================== CPU 特性 ==================== + + /** + * 获取 CPU 特性列表(从 /proc/cpuinfo 读取 Features) + * @return CPU 特性列表 + */ + fun getCpuFeatures(): List { + return try { + File("/proc/cpuinfo").readLines() + .find { it.startsWith("Features") } + ?.substringAfter(":") + ?.trim() + ?.split("\\s+".toRegex()) ?: emptyList() + } catch (e: Exception) { + emptyList() + } + } + + // ==================== /proc/cpuinfo 原始信息 ==================== + + /** + * 获取 /proc/cpuinfo 的完整内容 + * @return /proc/cpuinfo 文件内容 + */ + fun getProcCpuInfo(): String { + return try { + File("/proc/cpuinfo").readText() + } catch (e: Exception) { + "无法读取 /proc/cpuinfo" + } + } + + /** + * 解析 /proc/cpuinfo 为键值对映射 + * @return 解析后的映射表 + */ + fun parseProcCpuInfo(): Map { + val result = mutableMapOf() + try { + File("/proc/cpuinfo").readLines().forEach { line -> + if (line.contains(":")) { + val parts = line.split(":", limit = 2) + if (parts.size == 2) { + result[parts[0].trim()] = parts[1].trim() + } + } + } + } catch (e: Exception) { + // 忽略错误 + } + return result + } + + // ==================== 兼容性方法 ==================== + + /** + * 获取硬件名称(兼容旧版本) + * @return 处理器名称 + */ + @Deprecated("使用 getProcessorName() 替代", ReplaceWith("getProcessorName()")) + fun hardware(): String { + return getProcessorName() + } + + /** + * 获取文本格式的 CPU 信息摘要 + * @return 格式化的文本信息 + */ fun text(): String { - val stringBuilder = StringBuilder() - stringBuilder.append("处理器数量: ${processors.size}\n") - processors.forEachIndexed { index, processor -> - stringBuilder.append(" 处理器 ${index}: ${processor.cpuPackage?.name ?: "未知"}\n") - stringBuilder.append(" 核心: ${processor.core?.coreId ?: "未知"}\n") - stringBuilder.append(" 集群: ${processor.cluster?.clusterId ?: "未知"}\n") + val sb = StringBuilder() + + // 基本信息 + sb.append("=== CPU 基本信息 ===\n") + sb.append("处理器名称: ${getProcessorName()}\n") + sb.append("供应商: ${getVendor()}\n") + val processInfo = getProcessInfo() + sb.append("制程工艺: ${processInfo.process}\n") + sb.append("代工厂: ${processInfo.foundry}\n") + if (processInfo.node.isNotEmpty()) { + sb.append("制程节点: ${processInfo.node}\n") } - stringBuilder.append("核心数量: ${cores.size}\n") - cores.forEachIndexed { index, core -> - stringBuilder.append(" 核心 ${index}: ID=${core.coreId}, 频率=${core.frequency}Hz\n") + sb.append("物理核心数: ${getCoreCount()}\n") + sb.append("逻辑处理器数: ${getProcessorCount()}\n") + + // 大小核信息 + val clusterInfo = getClusterInfo() + sb.append("\n=== 大小核配置 ===\n") + if (clusterInfo.bigCoreCount > 0) { + sb.append("大核: ${clusterInfo.bigCoreCount} 个, 最高频率: ${formatFrequency(clusterInfo.bigCoreFreq)}\n") + } + if (clusterInfo.midCoreCount > 0) { + sb.append("中核: ${clusterInfo.midCoreCount} 个, 最高频率: ${formatFrequency(clusterInfo.midCoreFreq)}\n") + } + if (clusterInfo.littleCoreCount > 0) { + sb.append("小核: ${clusterInfo.littleCoreCount} 个, 最高频率: ${formatFrequency(clusterInfo.littleCoreFreq)}\n") + } + + // 架构信息 + sb.append("\n=== 架构信息 ===\n") + sb.append("架构: ${getAllArchitectures().joinToString(", ")}\n") + sb.append("ABI: ${getAbi()}\n") + sb.append("支持的 ABI: ${getSupportedAbis().joinToString(", ")}\n") + + // 调频器 + sb.append("\n=== 调频器 ===\n") + sb.append("当前调频器: ${getGovernor(0)}\n") + sb.append("可用调频器: ${getAvailableGovernors().joinToString(", ")}\n") + + // 核心详细频率 + sb.append("\n=== 核心频率信息 ===\n") + getCoreFrequencies().forEach { freq -> + sb.append("CPU${freq.coreId}: 当前=${formatFrequency(freq.currentFreq.toULong() * 1000UL)}, ") + sb.append("范围=${formatFrequency(freq.minFreq.toULong() * 1000UL)}-${formatFrequency(freq.maxFreq.toULong() * 1000UL)}\n") + } + + // 缓存信息 + sb.append("\n=== 缓存信息 ===\n") + sb.append("L1i 缓存: ${l1iCaches.size} 个\n") + sb.append("L1d 缓存: ${l1dCaches.size} 个\n") + sb.append("L2 缓存: ${l2Caches.size} 个\n") + sb.append("L3 缓存: ${l3Caches.size} 个\n") + + // CPU 特性 + val features = getCpuFeatures() + if (features.isNotEmpty()) { + sb.append("\n=== CPU 特性 ===\n") + sb.append(features.joinToString(" ")) + sb.append("\n") + } + + return sb.toString() + } + + /** + * 格式化频率显示 + * @param freqHz 频率(Hz) + * @return 格式化后的频率字符串 + */ + private fun formatFrequency(freqHz: ULong): String { + return when { + freqHz >= 1_000_000_000UL -> String.format("%.2f GHz", freqHz.toDouble() / 1_000_000_000) + freqHz >= 1_000_000UL -> String.format("%.0f MHz", freqHz.toDouble() / 1_000_000) + freqHz >= 1_000UL -> String.format("%.0f KHz", freqHz.toDouble() / 1_000) + else -> "$freqHz Hz" } - // 可以根据需要添加更多详细信息 - return stringBuilder.toString() } companion object { diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/DisplayInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/DisplayInfo.kt index ac76cf6..5bf26f6 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/DisplayInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/DisplayInfo.kt @@ -6,11 +6,20 @@ import android.os.Build import android.view.Display import android.graphics.Point import android.util.DisplayMetrics +import kotlin.math.sqrt + +/** + * 显示信息类,用于获取设备屏幕相关信息 + */ class DisplayInfo(private val context: Context) { private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + /** + * 获取默认显示器信息 + * @return 默认显示器信息对象,如果获取失败返回 null + */ fun getDefaultDisplayInfo(): DefaultDisplayInfo? { val defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY) ?: return null @@ -22,13 +31,24 @@ class DisplayInfo(private val context: Context) { val mode = defaultDisplay.mode val refreshRate = mode.refreshRate - - val isHdr = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - defaultDisplay.hdrCapabilities?.supportedHdrTypes?.isNotEmpty() ?: false + // 计算 PPI (每英寸像素数) + val ppi = calculatePPI(displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi) + // 获取支持的刷新率列表 + val supportedRefreshRates = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + defaultDisplay.supportedModes.map { it.refreshRate }.distinct().sorted() } else { - false + listOf(refreshRate) } + // 检查是否支持 HDR 及 HDR 类型 + val hdrTypes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + defaultDisplay.mode?.supportedHdrTypes?.map { getHdrTypeName(it) } ?: emptyList() + } else { + emptyList() + } + val isHdr = hdrTypes.isNotEmpty() + + // 检查是否支持广色域 val isWideColorGamut = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { defaultDisplay.isWideColorGamut } else { @@ -43,22 +63,81 @@ class DisplayInfo(private val context: Context) { densityDpi = displayMetrics.densityDpi, xdpi = displayMetrics.xdpi, ydpi = displayMetrics.ydpi, + ppi = ppi, refreshRate = refreshRate, + supportedRefreshRates = supportedRefreshRates, isHdr = isHdr, + hdrTypes, isWideColorGamut = isWideColorGamut ) } + /** + * 获取 HDR 类型名称 + * @param hdrType HDR 类型常量 + * @return HDR 类型名称 + */ + private fun getHdrTypeName(hdrType: Int): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + when (hdrType) { + Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION -> "Dolby Vision" + Display.HdrCapabilities.HDR_TYPE_HDR10 -> "HDR10" + Display.HdrCapabilities.HDR_TYPE_HLG -> "HLG" + Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS -> "HDR10+" + else -> "Unknown HDR Type ($hdrType)" + } + } else { + "Unknown" + } + } + /** + * 计算屏幕 PPI (每英寸像素数) + * @param widthPixels 屏幕宽度像素 + * @param heightPixels 屏幕高度像素 + * @param densityDpi 屏幕密度 DPI + * @return PPI 值 + */ + private fun calculatePPI(widthPixels: Int, heightPixels: Int, densityDpi: Int): Double { + // 使用勾股定理计算对角线像素数 + val diagonalPixels = sqrt((widthPixels * widthPixels + heightPixels * heightPixels).toDouble()) + + // 通过 densityDpi 计算屏幕对角线英寸数 + // 160 DPI 是 Android 的基准密度 (mdpi) + val diagonalInches = diagonalPixels / densityDpi + + // PPI = 对角线像素数 / 对角线英寸数 + return diagonalPixels / diagonalInches + } + + /** + * 默认显示器信息数据类 + * @property id 显示器 ID + * @property name 显示器名称 + * @property widthPixels 屏幕宽度(像素) + * @property heightPixels 屏幕高度(像素) + * @property densityDpi 屏幕密度 DPI + * @property xdpi X 轴每英寸像素数 + * @property ydpi Y 轴每英寸像素数 + * @property ppi 屏幕 PPI(每英寸像素数) + * @property refreshRate 当前刷新率(Hz) + * @property supportedRefreshRates 支持的刷新率列表(Hz) + * @property isHdr 是否支持 HDR + * @property hdrTypes 支持的 HDR 类型列表 + * @property isWideColorGamut 是否支持广色域 + */ data class DefaultDisplayInfo( - val id: Int, - val name: String, - val widthPixels: Int, - val heightPixels: Int, - val densityDpi: Int, - val xdpi: Float, - val ydpi: Float, - val refreshRate: Float, - val isHdr: Boolean, - val isWideColorGamut: Boolean + val id: Int, // 显示器 ID + val name: String, // 显示器名称 + val widthPixels: Int, // 屏幕宽度(像素) + val heightPixels: Int, // 屏幕高度(像素) + val densityDpi: Int, // 屏幕密度 DPI + val xdpi: Float, // X 轴 DPI + val ydpi: Float, // Y 轴 DPI + val ppi: Double, // 屏幕 PPI + val refreshRate: Float, // 当前刷新率(Hz) + val supportedRefreshRates: List, // 支持的刷新率列表(Hz) + val isHdr: Boolean, // 是否支持 HDR + val hdrTypes: List, // 支持的 HDR 类型列表 + val isWideColorGamut: Boolean // 是否支持广色域 ) } diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/MemInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/MemInfo.kt index 8b0e8fe..abaf916 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/MemInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/MemInfo.kt @@ -1,4 +1,142 @@ package com.xyzshell.andinfo.libs -class MemInfo { +import android.content.Context +import java.io.File +import java.io.RandomAccessFile + +data class MemoryInfo( + val totalRam: Long, // 总内存 (字节) + val availableRam: Long, // 可用内存 (字节) + val usedRam: Long, // 已使用内存 (字节) + val memType: String, // 内存类型 (LPDDR4/LPDDR5等) + val channels: Int, // 内存通道数 + val zramTotal: Long, // zram 总大小 (字节) + val zramUsed: Long // zram 已使用 (字节) +) + +class MemInfo(private val context: Context){ + + fun getMemoryInfo(): MemoryInfo { + val totalRam = getTotalMemory() + val availableRam = getAvailableMemory() + val usedRam = totalRam - availableRam + val memType = getMemoryType() + val channels = getMemoryChannels() + val (zramTotal, zramUsed) = getZramInfo() + + return MemoryInfo( + totalRam = totalRam, + availableRam = availableRam, + usedRam = usedRam, + memType = memType, + channels = channels, + zramTotal = zramTotal, + zramUsed = zramUsed + ) + } + + private fun getTotalMemory(): Long { + return try { + val memInfo = File("/proc/meminfo") + memInfo.readLines().find { it.startsWith("MemTotal:") }?.let { + it.split("\\s+".toRegex())[1].toLong() * 1024 + } ?: 0L + } catch (e: Exception) { + 0L + } + } + + private fun getAvailableMemory(): Long { + return try { + val memInfo = File("/proc/meminfo") + memInfo.readLines().find { it.startsWith("MemAvailable:") }?.let { + it.split("\\s+".toRegex())[1].toLong() * 1024 + } ?: 0L + } catch (e: Exception) { + 0L + } + } + + private fun getMemoryType(): String { + return try { + // 尝试从多个可能的位置读取 + val paths = listOf( + "/sys/class/devfreq/ddrfreq/device/type", + "/sys/class/devfreq/soc:qcom,cpu-cpu-llcc-bw/device/type", + "/proc/device-tree/memory/device_type" + ) + + for (path in paths) { + val file = File(path) + if (file.exists()) { + val content = file.readText().trim() + if (content.isNotEmpty()) { + return content + } + } + } + + // 尝试从 getprop 获取 + Runtime.getRuntime().exec("getprop ro.boot.ddr_type").inputStream.bufferedReader().readText().trim() + .ifEmpty { "Unknown" } + } catch (e: Exception) { + "Unknown" + } + } + + private fun getMemoryChannels(): Int { + return try { + // 尝试从设备树或内核日志获取 + val dmesgChannels = Runtime.getRuntime() + .exec("dmesg | grep -i 'memory.*channel'") + .inputStream.bufferedReader().readText() + + // 解析通道数,默认返回 2 + when { + dmesgChannels.contains("dual", ignoreCase = true) -> 2 + dmesgChannels.contains("quad", ignoreCase = true) -> 4 + dmesgChannels.contains("single", ignoreCase = true) -> 1 + else -> 2 // 默认双通道 + } + } catch (e: Exception) { + 2 + } + } + + private fun getZramInfo(): Pair { + return try { + var total = 0L + var used = 0L + + // 检查所有 zram 设备 + for (i in 0..7) { + val devicePath = "/sys/block/zram$i" + if (!File(devicePath).exists()) continue + + // 读取总大小 + File("$devicePath/disksize").readText().trim().toLongOrNull()?.let { + total += it + } + + // 读取已使用大小 + File("$devicePath/mem_used_total").readText().trim().toLongOrNull()?.let { + used += it + } + } + + Pair(total, used) + } catch (e: Exception) { + Pair(0L, 0L) + } + } + + // 格式化内存大小 + fun formatBytes(bytes: Long): String { + return when { + bytes < 1024 -> "$bytes B" + bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0) + bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024)) + else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024)) + } + } } \ No newline at end of file diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/NetworkInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/NetworkInfo.kt new file mode 100644 index 0000000..757063e --- /dev/null +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/NetworkInfo.kt @@ -0,0 +1,511 @@ +package com.xyzshell.andinfo.libs + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.net.* +import android.net.wifi.ScanResult +import android.net.wifi.WifiManager +import android.net.wifi.WifiInfo +import android.os.Build +import android.telephony.* +import androidx.annotation.RequiresPermission +import androidx.core.app.ActivityCompat +import kotlin.text.compareTo + +/** + * 网络信息工具类,提供当前网络类型、Wi-Fi详情、移动数据详情等获取方法 + */ +class NetworkInfo(private val context: Context) { + + /** + * 当前网络类型枚举 + */ + enum class NetworkType { + WIFI, MOBILE, NONE + } + + /** + * Wi-Fi详细信息数据结构 + */ + data class WifiDetails( + val connected: Boolean, // 是否已连接 + val ssid: String?, // SSID名称 + val bssid: String?, // BSSID + val capabilities: String?, // 加密/功能信息 + val linkSpeedMbps: Int?, // 连接速率 + val rssi: Int?, // dBm信号强度 + val signalLevelPercent: Int?, // 信号强度百分比 + val frequency: Int?, // 频率(MHz) + val channel: Int?, // 信道 + val standard: String?, // 当前连接的标准 + val dhcpServer: String?, // DHCP服务器 + val leaseDuration: Int?, // DHCP租约期限(秒) + val gateway: String?, // 网关 + val subnetMask: String?, // 子网掩码 + val dns1: String?, // DNS1 + val dns2: String?, // DNS2 + val ip: String?, // IPv4地址 + val ipv6: List?, // IPv6地址列表 + val supportedStandards: List?, // 硬件支持的Wi-Fi标准 + val wifiDirect: Boolean?, // 是否支持Wi-Fi Direct + val wifiAware: Boolean?, // 是否支持Wi-Fi Aware + val wifiPasspoint: Boolean?, // 是否支持Passpoint + val support5G: Boolean?, // 是否支持5GHz Wi-Fi + val support6G: Boolean?, // 是否支持6GHz Wi-Fi + val support60G: Boolean? // 是否支持60GHz Wi-Fi + ) + + /** + * SIM卡信息数据结构 + */ + data class SimInfo( + val slotIndex: Int, // 卡槽索引 + val carrierName: String?, // 运营商名称 + val operator: String?, // 运营商代码(MCC+MNC) + val countryIso: String?, // 国家代码 + val roaming: Boolean, // 是否漫游 + val simState: Int, // SIM卡状态 + val simStateText: String, // SIM卡状态文本 + val phoneType: Int, // 手机类型 + val phoneTypeText: String, // 手机类型文本 + val networkType: Int, // 网络类型 + val networkTypeText: String, // 网络类型文本 + val isEsim: Boolean // 是否eSIM + ) + + /** + * 移动数据详细信息数据结构 + */ + data class MobileDetails( + val dualSim: Boolean, // 是否双卡 + val phoneCount: Int, // 卡槽数量 + val phoneType: Int, // 手机类型 + val esimSupported: Boolean, // 是否支持eSIM + val dataEnabled: Boolean, // 移动数据是否开启 + val dataSim: Int?, // 默认数据网络类型 + val voiceSim: Int?, // 默认语音网络类型 + val smsSim: Int?, // 默认短信卡(Android无直接API) + val simInfos: List // SIM卡信息列表 + ) + + /** + * 获取当前网络类型(Wi-Fi/移动数据/无网络) + */ + fun getCurrentNetworkType(): NetworkType { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + ?: return NetworkType.NONE + + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val active = cm.activeNetwork ?: return NetworkType.NONE + val caps = cm.getNetworkCapabilities(active) ?: return NetworkType.NONE + when { + caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkType.WIFI + caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkType.MOBILE + else -> NetworkType.NONE + } + } else { + @Suppress("DEPRECATION") + when (cm.activeNetworkInfo?.type) { + ConnectivityManager.TYPE_WIFI -> NetworkType.WIFI + ConnectivityManager.TYPE_MOBILE -> NetworkType.MOBILE + else -> NetworkType.NONE + } + } + } catch (e: Exception) { + NetworkType.NONE + } + } + + /** + * 获取当前Wi-Fi详细信息 + * 需要权限: ACCESS_WIFI_STATE, (Android 10+) + */ + fun getWifiDetails(): WifiDetails? { + val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager + ?: return null + + // 检查权限 + if (!checkWifiPermissions()) { + return null + } + + return try { + val wifiInfo = wifiManager.connectionInfo ?: return createEmptyWifiDetails() + val connected = wifiInfo.networkId != -1 + + if (!connected) return createEmptyWifiDetails() + + val dhcp = wifiManager.dhcpInfo + val ssid = wifiInfo.ssid?.removePrefix("\"")?.removeSuffix("\"") + val bssid = wifiInfo.bssid + val linkSpeed = wifiInfo.linkSpeed + val rssi = wifiInfo.rssi + val freq = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + wifiInfo.frequency + } else null + val channel = freq?.let { calculateChannel(it) } + val level = if (rssi != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WifiManager.calculateSignalLevel(rssi, 100) + } else { + @Suppress("DEPRECATION") + WifiManager.calculateSignalLevel(rssi, 100) + } + } else null + + // 获取加密能力信息 + val capabilities = try { + wifiManager.scanResults.find { it.BSSID == bssid }?.capabilities + } catch (e: SecurityException) { + null + } + + // 获取当前Wi-Fi标准 + val standard = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + when (wifiInfo.wifiStandard) { + ScanResult.WIFI_STANDARD_LEGACY -> "802.11a/b/g" + ScanResult.WIFI_STANDARD_11N -> "802.11n (Wi-Fi 4)" + ScanResult.WIFI_STANDARD_11AC -> "802.11ac (Wi-Fi 5)" + ScanResult.WIFI_STANDARD_11AX -> "802.11ax (Wi-Fi 6/6E)" + else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + when (wifiInfo.wifiStandard) { + ScanResult.WIFI_STANDARD_11AD -> "802.11ad (WiGig)" + else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + when (wifiInfo.wifiStandard) { + ScanResult.WIFI_STANDARD_11BE -> "802.11be (Wi-Fi 7)" + else -> "Unknown" + } + } else "Unknown" + } + } else "Unknown" + } + } else null + + val dhcpServer = dhcp?.serverAddress?.let { intToIp(it) } + val leaseDuration = dhcp?.leaseDuration + val gateway = dhcp?.gateway?.let { intToIp(it) } + val subnetMask = dhcp?.netmask?.let { intToIp(it) } + val dns1 = dhcp?.dns1?.let { intToIp(it) } + val dns2 = dhcp?.dns2?.let { intToIp(it) } + val ip = dhcp?.ipAddress?.let { intToIp(it) } + val ipv6 = getIpv6Addresses() + val supportedStandards = getSupportedWifiStandards(wifiManager) + + // Wi-Fi Direct (P2P) 支持 + val wifiDirect = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT) + } else null + + // Wi-Fi Aware (NAN) 支持 + val wifiAware = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE) + } else null + + // Passpoint (Hotspot 2.0) 支持 + val wifiPasspoint = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + try { + wifiManager.isWifiPasspointEnabled + } catch (e: NoSuchMethodError) { + // 某些设备可能不支持此方法 + null + } catch (e: Exception) { + null + } + } else null + + val support5G = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + wifiManager.is5GHzBandSupported + } else null + + val support6G = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + wifiManager.is6GHzBandSupported + } else null + + val support60G = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + wifiManager.is60GHzBandSupported + } else null + + WifiDetails( + connected, ssid, bssid, capabilities, linkSpeed, rssi, level, freq, channel, standard, + dhcpServer, leaseDuration, gateway, subnetMask, dns1, dns2, ip, ipv6, + supportedStandards, wifiDirect, wifiAware, wifiPasspoint, support5G, support6G, support60G + ) + } catch (e: Exception) { + null + } + } + + /** + * 获取移动数据详细信息,包括双卡、eSIM、SIM卡状态等 + * 需要权限: READ_PHONE_STATE + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + fun getMobileDetails(): MobileDetails? { + if (!checkPhonePermissions()) { + return null + } + + val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager + ?: return null + + return try { + val subscriptionManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as? SubscriptionManager + } else null + + val sims = mutableListOf() + val esimSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + tm.isMultiSimSupported == TelephonyManager.MULTISIM_ALLOWED && + context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC) + } else false + + val phoneType = tm.phoneType + val phoneCount = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + tm.phoneCount + } else 1 + + val dualSim = phoneCount > 1 + + val dataEnabled = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + tm.isDataEnabled + } else { + @Suppress("DEPRECATION") + tm.dataState == TelephonyManager.DATA_CONNECTED + } + + val dataSim = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tm.dataNetworkType + } else null + + val voiceSim = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tm.voiceNetworkType + } else null + + val smsSim = null // Android没有直接获取默认短信卡的API + + // 获取每个SIM卡的详细信息 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && subscriptionManager != null) { + val subInfoList = subscriptionManager.activeSubscriptionInfoList ?: emptyList() + for (info in subInfoList) { + val subTm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tm.createForSubscriptionId(info.subscriptionId) + } else tm + + sims.add( + SimInfo( + slotIndex = info.simSlotIndex, + carrierName = info.carrierName?.toString(), + operator = subTm.simOperator, + countryIso = subTm.simCountryIso, + roaming = subTm.isNetworkRoaming, + simState = subTm.simState, + simStateText = getSimStateText(subTm.simState), + phoneType = subTm.phoneType, + phoneTypeText = getPhoneTypeText(subTm.phoneType), + networkType = subTm.networkType, + networkTypeText = getNetworkTypeText(subTm.networkType), + isEsim = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + info.isEmbedded + } else false + ) + ) + } + } else { + // 旧版本系统,只能获取单卡信息 + sims.add( + SimInfo( + slotIndex = 0, + carrierName = tm.simOperatorName, + operator = tm.simOperator, + countryIso = tm.simCountryIso, + roaming = tm.isNetworkRoaming, + simState = tm.simState, + simStateText = getSimStateText(tm.simState), + phoneType = tm.phoneType, + phoneTypeText = getPhoneTypeText(tm.phoneType), + networkType = tm.networkType, + networkTypeText = getNetworkTypeText(tm.networkType), + isEsim = esimSupported + ) + ) + } + + MobileDetails( + dualSim = dualSim, + phoneCount = phoneCount, + phoneType = phoneType, + esimSupported = esimSupported, + dataEnabled = dataEnabled, + dataSim = dataSim, + voiceSim = voiceSim, + smsSim = smsSim, + simInfos = sims + ) + } catch (e: Exception) { + null + } + } + + /** + * int型IP转为字符串 + */ + private fun intToIp(ip: Int): String = + "${ip and 0xFF}.${ip shr 8 and 0xFF}.${ip shr 16 and 0xFF}.${ip shr 24 and 0xFF}" + + /** + * 获取所有IPv6地址 + */ + private fun getIpv6Addresses(): List { + val result = mutableListOf() + try { + val interfaces = java.net.NetworkInterface.getNetworkInterfaces() + for (iface in interfaces) { + if (!iface.isUp || iface.isLoopback) continue + for (addr in iface.inetAddresses) { + if (addr is java.net.Inet6Address && !addr.isLoopbackAddress && !addr.isLinkLocalAddress) { + result.add(addr.hostAddress ?: continue) + } + } + } + } catch (_: Exception) {} + return result + } + + /** + * 获取设备支持的Wi-Fi标准 + */ + private fun getSupportedWifiStandards(wifiManager: WifiManager): List { + val list = mutableListOf() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (wifiManager.isWifiStandardSupported(android.net.wifi.ScanResult.WIFI_STANDARD_LEGACY)) { + list.add("802.11a/b/g") + } + if (wifiManager.isWifiStandardSupported(android.net.wifi.ScanResult.WIFI_STANDARD_11N)) { + list.add("802.11n (Wi-Fi 4)") + } + if (wifiManager.isWifiStandardSupported(android.net.wifi.ScanResult.WIFI_STANDARD_11AC)) { + list.add("802.11ac (Wi-Fi 5)") + } + if (wifiManager.isWifiStandardSupported(android.net.wifi.ScanResult.WIFI_STANDARD_11AX)) { + list.add("802.11ax (Wi-Fi 6/6E)") + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (wifiManager.isWifiStandardSupported(android.net.wifi.ScanResult.WIFI_STANDARD_11AD)) { + list.add("802.11ad (WiGig)") + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (wifiManager.isWifiStandardSupported(android.net.wifi.ScanResult.WIFI_STANDARD_11BE)) { + list.add("802.11be (Wi-Fi 7)") + } + } + } + return list + } + + /** + * 根据频率计算信道 + */ + private fun calculateChannel(frequency: Int): Int { + return when { + frequency in 2412..2484 -> (frequency - 2407) / 5 // 2.4GHz + frequency in 5170..5825 -> (frequency - 5000) / 5 // 5GHz + frequency in 5955..7115 -> (frequency - 5950) / 5 // 6GHz + else -> -1 + } + } + + /** + * 创建空的Wi-Fi详情对象 + */ + private fun createEmptyWifiDetails() = WifiDetails( + false, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, + null, support60G = null + ) + + /** + * 检查Wi-Fi相关权限 + */ + private fun checkWifiPermissions(): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return ActivityCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + return ActivityCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_WIFI_STATE + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * 检查电话相关权限 + */ + private fun checkPhonePermissions(): Boolean { + return ActivityCompat.checkSelfPermission( + context, + Manifest.permission.READ_PHONE_STATE + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * 获取SIM卡状态文本 + */ + private fun getSimStateText(state: Int): String = when (state) { + TelephonyManager.SIM_STATE_ABSENT -> "无卡" + TelephonyManager.SIM_STATE_PIN_REQUIRED -> "需要PIN" + TelephonyManager.SIM_STATE_PUK_REQUIRED -> "需要PUK" + TelephonyManager.SIM_STATE_NETWORK_LOCKED -> "网络锁定" + TelephonyManager.SIM_STATE_READY -> "就绪" + TelephonyManager.SIM_STATE_NOT_READY -> "未就绪" + TelephonyManager.SIM_STATE_PERM_DISABLED -> "永久禁用" + TelephonyManager.SIM_STATE_CARD_IO_ERROR -> "卡IO错误" + TelephonyManager.SIM_STATE_CARD_RESTRICTED -> "卡受限" + else -> "未知" + } + + /** + * 获取手机类型文本 + */ + private fun getPhoneTypeText(type: Int): String = when (type) { + TelephonyManager.PHONE_TYPE_NONE -> "无" + TelephonyManager.PHONE_TYPE_GSM -> "GSM" + TelephonyManager.PHONE_TYPE_CDMA -> "CDMA" + TelephonyManager.PHONE_TYPE_SIP -> "SIP" + else -> "未知" + } + + /** + * 获取网络类型文本 + */ + private fun getNetworkTypeText(type: Int): String = when (type) { + TelephonyManager.NETWORK_TYPE_GPRS -> "GPRS" + TelephonyManager.NETWORK_TYPE_EDGE -> "EDGE" + TelephonyManager.NETWORK_TYPE_UMTS -> "UMTS (3G)" + TelephonyManager.NETWORK_TYPE_CDMA -> "CDMA" + TelephonyManager.NETWORK_TYPE_EVDO_0 -> "EVDO 0" + TelephonyManager.NETWORK_TYPE_EVDO_A -> "EVDO A" + TelephonyManager.NETWORK_TYPE_1xRTT -> "1xRTT" + TelephonyManager.NETWORK_TYPE_HSDPA -> "HSDPA" + TelephonyManager.NETWORK_TYPE_HSUPA -> "HSUPA" + TelephonyManager.NETWORK_TYPE_HSPA -> "HSPA" + TelephonyManager.NETWORK_TYPE_IDEN -> "iDEN" + TelephonyManager.NETWORK_TYPE_EVDO_B -> "EVDO B" + TelephonyManager.NETWORK_TYPE_LTE -> "LTE (4G)" + TelephonyManager.NETWORK_TYPE_EHRPD -> "eHRPD" + TelephonyManager.NETWORK_TYPE_HSPAP -> "HSPA+" + TelephonyManager.NETWORK_TYPE_GSM -> "GSM" + TelephonyManager.NETWORK_TYPE_TD_SCDMA -> "TD-SCDMA" + TelephonyManager.NETWORK_TYPE_IWLAN -> "IWLAN" + else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + when (type) { + TelephonyManager.NETWORK_TYPE_NR -> "5G NR" + else -> "未知" + } + } else "未知" + } +} \ No newline at end of file diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt index 844e5a3..1cf0118 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt @@ -5,22 +5,27 @@ import android.os.Environment import android.os.StatFs import com.xyzshell.andinfo.libs.storage.models.EncryptionType import com.xyzshell.andinfo.utils.SystemProperties +import java.io.File class StorageInfo(private val context: Context) { + // 内部存储总空间 val internalStorageTotalSpace: Long get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs -> statFs.blockCountLong * statFs.blockSizeLong } + // 内部存储可用空间 val internalStorageAvailableSpace: Long get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs -> statFs.availableBlocksLong * statFs.blockSizeLong } + // 内部存储已使用空间 val internalStorageUsedSpace: Long get() = internalStorageTotalSpace - internalStorageAvailableSpace + // 内部存储是否加密 val isInternalStorageEncrypted: Boolean? get() = when (SystemProperties.getString("ro.crypto.state", "unknown")) { "encrypted" -> true @@ -28,6 +33,7 @@ class StorageInfo(private val context: Context) { else -> null } + // 内部存储加密类型 val internalStorageEncryptionType: EncryptionType? get() = when (SystemProperties.getString("ro.crypto.type", "unknown")) { "none" -> EncryptionType.NONE @@ -36,6 +42,58 @@ class StorageInfo(private val context: Context) { else -> null } + // 内部存储文件系统类型 + val internalStorageFileSystemType: String? + get() = try { + // 方法1: 读取 /proc/mounts 文件 + File("/proc/mounts").readLines() + .find { it.contains(" /data ") } + ?.split("\\s+".toRegex()) + ?.getOrNull(2) + ?: run { + // 方法2: 使用 StatFs (Android 8.0+) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + val statFs = StatFs("/data") + statFs.javaClass.getMethod("getFileSystemName").invoke(statFs) as? String + } else { + null + } + } + } catch (e: Exception) { + null + } + + // /data 目录总大小 + val dataDirectoryTotalSpace: Long + get() = StatFs("/data").let { statFs -> + statFs.blockCountLong * statFs.blockSizeLong + } + + // /data 目录已使用空间 + val dataDirectoryUsedSpace: Long + get() = StatFs("/data").let { statFs -> + val total = statFs.blockCountLong * statFs.blockSizeLong + val available = statFs.availableBlocksLong * statFs.blockSizeLong + total - available + } + + // 应用程序和数据占用大小 + val applicationsAndDataSize: Long + get() = try { + val dataDir = File("/data/data") + val appDir = File("/data/app") + calculateDirectorySize(dataDir) + calculateDirectorySize(appDir) + } catch (e: Exception) { + 0L + } + + // 系统占用大小 + val systemSize: Long + get() = StatFs(Environment.getRootDirectory().absolutePath).let { statFs -> + statFs.blockCountLong * statFs.blockSizeLong + } + + // 外部存储总空间 val externalStorageTotalSpace: Long? get() = runCatching { StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs -> @@ -43,6 +101,7 @@ class StorageInfo(private val context: Context) { } }.getOrNull() + // 外部存储可用空间 val externalStorageAvailableSpace: Long? get() = runCatching { StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs -> @@ -50,6 +109,7 @@ class StorageInfo(private val context: Context) { } }.getOrNull() + // 外部存储已使用空间 val externalStorageUsedSpace: Long? get() = externalStorageTotalSpace?.let { total -> externalStorageAvailableSpace?.let { available -> @@ -57,36 +117,76 @@ class StorageInfo(private val context: Context) { } } + // 外部存储是否为模拟存储 val isExternalStorageEmulated: Boolean get() = Environment.isExternalStorageEmulated() + // 外部存储是否可移除 val isExternalStorageRemovable: Boolean get() = Environment.isExternalStorageRemovable() + // 是否支持可更新的 APEX val hasUpdatableApex: Boolean? get() = SystemProperties.getBoolean("ro.apex.updatable") + // 是否使用 system-as-root 分区方案 val usesSystemAsRoot: Boolean? get() = SystemProperties.getBoolean("ro.build.system_root_image") + // 是否使用 A/B 分区更新方案 val usesAb: Boolean? get() = SystemProperties.getBoolean("ro.build.ab_update") + // A/B OTA 分区列表 val abOtaPartitions: Array? get() = SystemProperties.getString("ro.product.ab_ota_partitions")?.split(",")?.toTypedArray() + // 是否使用动态分区 val usesDynamicPartitions: Boolean? get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions") + // 是否使用改造的动态分区 val usesRetrofittedDynamicPartitions: Boolean? get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions_retrofit") + // 是否使用虚拟 A/B 分区 val usesVirtualAb: Boolean? get() = SystemProperties.getBoolean("ro.virtual_ab.enabled") + // 是否使用改造的虚拟 A/B 分区 val usesRetrofittedVirtualAb: Boolean? get() = SystemProperties.getBoolean("ro.virtual_ab.retrofit") + // 虚拟 A/B 分区是否启用压缩 val usesCompressedVirtualAb: Boolean? get() = SystemProperties.getBoolean("ro.virtual_ab.compression.enabled") + + // 计算目录大小(递归) + private fun calculateDirectorySize(directory: File): Long { + var size = 0L + try { + if (directory.exists() && directory.isDirectory) { + directory.listFiles()?.forEach { file -> + size += if (file.isDirectory) { + calculateDirectorySize(file) + } else { + file.length() + } + } + } + } catch (e: Exception) { + // 可能因权限问题无法访问某些目录 + } + return size + } + + // 格式化存储大小 + fun formatBytes(bytes: Long): String { + return when { + bytes < 1024 -> "$bytes B" + bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0) + bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024)) + else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024)) + } + } } diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/utils/WebService.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/utils/WebService.kt new file mode 100644 index 0000000..14c96cc --- /dev/null +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/utils/WebService.kt @@ -0,0 +1,112 @@ +package com.xyzshell.andinfo.utils + +import android.util.Base64 +import com.google.gson.Gson +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + +class WebService { + private val client = OkHttpClient() + val gson = Gson() + // AES 密钥 (必须是 16/24/32 字节) + private val aesKey = "e67cbcee5e573d1b" + + // AES 加密 + fun encrypt(plainText: String): String { + return try { + val secretKey = SecretKeySpec(aesKey.toByteArray(), "AES") + val cipher = Cipher.getInstance("AES") + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + val encryptedBytes = cipher.doFinal(plainText.toByteArray()) + Base64.encodeToString(encryptedBytes, Base64.NO_WRAP) + } catch (e: Exception) { + throw Exception("加密失败: ${e.message}") + } + } + fun postData(url: String, jsonData: String, callback: (String?, Exception?) -> Unit) { + val mediaType = "application/json; charset=utf-8".toMediaType() + val body = jsonData.toRequestBody(mediaType) + + val request = Request.Builder() + .url(url) + .post(body) + .build() + + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + callback(null, e) + } + + override fun onResponse(call: Call, response: Response) { + response.use { + if (it.isSuccessful) { + callback(it.body?.string(), null) + } else { + callback(null, Exception("HTTP ${it.code}")) + } + } + } + }) + } + + // 同步版本 + fun postDataSync(url: String, jsonData: String): String? { + val mediaType = "application/json; charset=utf-8".toMediaType() + val body = jsonData.toRequestBody(mediaType) + + val request = Request.Builder() + .url(url) + .post(body) + .build() + + client.newCall(request).execute().use { response -> + return if (response.isSuccessful) { + response.body?.string() + } else { + null + } + } + } + + // 序列化对象为 JSON + fun toJson(obj: T): String { + return gson.toJson(obj) + } + + // 反序列化 JSON 为对象 + inline fun fromJson(json: String): T? { + return try { + gson.fromJson(json, T::class.java) + } catch (e: Exception) { + null + } + } + + // 带序列化的 POST 请求 + fun postObject(url: String, obj: T, callback: (String?, Exception?) -> Unit) { + val jsonData = toJson(obj) + postData(url, jsonData, callback) + } + // 带序列化和反序列化的 POST 请求 + inline fun postAndParse( + url: String, + obj: T, + crossinline callback: (R?, Exception?) -> Unit + ) { + val jsonData = toJson(obj) + postData(url, jsonData) { response, error -> + if (error != null) { + callback(null, error) + } else if (response != null) { + val result = fromJson(response) + callback(result, null) + } else { + callback(null, Exception("Empty response")) + } + } + } +} \ No newline at end of file