Merge branch 'main' of http://git.zhenbs.com:9999/2-group-android/DevCheck-lib
This commit is contained in:
commit
22f16d7595
@ -7,7 +7,7 @@ apiVersion = "82"
|
|||||||
datastore = "1.1.7"
|
datastore = "1.1.7"
|
||||||
datastorePreferences = "1.1.7"
|
datastorePreferences = "1.1.7"
|
||||||
googleSymbolProcessingApi = "2.0.0-1.0.24"
|
googleSymbolProcessingApi = "2.0.0-1.0.24"
|
||||||
gson = "2.13.1"
|
gson = "2.13.2"
|
||||||
jsonVersion = "20250517"
|
jsonVersion = "20250517"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.16.0"
|
coreKtx = "1.16.0"
|
||||||
|
|||||||
@ -50,6 +50,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
// implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7")
|
// implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7")
|
||||||
implementation(libs.androidx.datastore)
|
implementation(libs.androidx.datastore)
|
||||||
|
implementation(libs.squareup.okhttp)
|
||||||
|
implementation(libs.gson)
|
||||||
// implementation("androidx.datastore:datastore-rxjava3:1.1.7")
|
// implementation("androidx.datastore:datastore-rxjava3:1.1.7")
|
||||||
implementation("androidx.security:security-state:1.0.0-alpha04")
|
implementation("androidx.security:security-state:1.0.0-alpha04")
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
|||||||
@ -2,44 +2,517 @@ package com.xyzshell.andinfo.libs
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothManager
|
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.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蓝牙信息工具类
|
||||||
|
* 提供蓝牙适配器信息、已配对设备、附近设备扫描、蓝牙特性检测等功能
|
||||||
|
*/
|
||||||
class BluetoothInfo(private val context: Context) {
|
class BluetoothInfo(private val context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蓝牙管理器
|
||||||
|
*/
|
||||||
private val bluetoothManager: BluetoothManager? = context.getSystemService(
|
private val bluetoothManager: BluetoothManager? = context.getSystemService(
|
||||||
BluetoothManager::class.java
|
BluetoothManager::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蓝牙适配器
|
||||||
|
*/
|
||||||
@get:SuppressLint("MissingPermission")
|
@get:SuppressLint("MissingPermission")
|
||||||
val bluetoothAdapter by lazy { bluetoothManager?.adapter }
|
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<String>? = 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")
|
@get:SuppressLint("MissingPermission")
|
||||||
val adapterName: String?
|
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")
|
@get:SuppressLint("HardwareIds", "MissingPermission")
|
||||||
val adapterMacAddress: String?
|
val adapterMacAddress: String?
|
||||||
get() = bluetoothAdapter?.address
|
get() = if (checkBluetoothConnectPermission()) {
|
||||||
|
bluetoothAdapter?.address
|
||||||
|
} else null
|
||||||
|
|
||||||
@get:SuppressLint("MissingPermission")
|
/**
|
||||||
val bondedDevices: List<Pair<String?, String>>
|
* 蓝牙是否已启用
|
||||||
get() = bluetoothAdapter?.bondedDevices?.map {
|
*/
|
||||||
Pair(it.name, it.address)
|
val isEnabled: Boolean
|
||||||
} ?: emptyList()
|
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
|
val isBluetoothLeSupported: Boolean
|
||||||
get() = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
|
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<Pair<String?, String>>
|
||||||
|
get() = if (checkBluetoothConnectPermission()) {
|
||||||
|
bluetoothAdapter?.bondedDevices?.map {
|
||||||
|
Pair(it.name, it.address)
|
||||||
|
} ?: emptyList()
|
||||||
|
} else emptyList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已配对设备详细信息列表
|
||||||
|
* 需要权限: BLUETOOTH_CONNECT (Android 12+)
|
||||||
|
*/
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
fun getBondedDevicesInfo(): List<DeviceInfo> {
|
||||||
|
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<String>
|
val requiredPermissions: Array<String>
|
||||||
get() = buildList {
|
get() = buildList {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
add(Manifest.permission.BLUETOOTH_CONNECT)
|
add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||||
|
add(Manifest.permission.BLUETOOTH_SCAN)
|
||||||
} else {
|
} else {
|
||||||
add(Manifest.permission.BLUETOOTH)
|
add(Manifest.permission.BLUETOOTH)
|
||||||
add(Manifest.permission.BLUETOOTH_ADMIN)
|
add(Manifest.permission.BLUETOOTH_ADMIN)
|
||||||
}
|
}
|
||||||
|
add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
}.toTypedArray()
|
}.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,101 +5,257 @@ import android.os.Build
|
|||||||
// import androidx.security.state.SecurityStateManagerCompat
|
// import androidx.security.state.SecurityStateManagerCompat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统构建信息工具类
|
||||||
|
* 提供 Android 系统构建版本、编译信息、安全补丁、JVM 信息等
|
||||||
|
*/
|
||||||
class BuildInfo(private val context: Context) {
|
class BuildInfo(private val context: Context) {
|
||||||
|
|
||||||
// private val securityStateManager = SecurityStateManagerCompat(context)
|
// private val securityStateManager = SecurityStateManagerCompat(context)
|
||||||
// private val globalSecurityState = securityStateManager.getGlobalSecurityState()
|
// 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
|
val fingerprint: String
|
||||||
get() = Build.FINGERPRINT
|
get() = Build.FINGERPRINT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建标签
|
||||||
|
* 通常为 "release-keys" (正式版) 或 "test-keys" (测试版)
|
||||||
|
*/
|
||||||
val tags: String
|
val tags: String
|
||||||
get() = Build.TAGS
|
get() = Build.TAGS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建类型
|
||||||
|
* 通常为 "user" (用户版)、"userdebug" (调试版) 或 "eng" (工程版)
|
||||||
|
*/
|
||||||
val type: String
|
val type: String
|
||||||
get() = Build.TYPE
|
get() = Build.TYPE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统构建日期
|
||||||
|
* 返回构建时的时间戳
|
||||||
|
*/
|
||||||
val buildDate: Date
|
val buildDate: Date
|
||||||
get() = Date(Build.TIME)
|
get() = Date(Build.TIME)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建主机名
|
||||||
|
* 编译系统的主机名
|
||||||
|
*/
|
||||||
val host: String
|
val host: String
|
||||||
get() = Build.HOST
|
get() = Build.HOST
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建用户
|
||||||
|
* 执行编译的用户名
|
||||||
|
*/
|
||||||
val user: String
|
val user: String
|
||||||
get() = Build.USER
|
get() = Build.USER
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 ID
|
||||||
|
* 构建的唯一标识符,通常是版本号
|
||||||
|
* 示例: TP1A.220905.004
|
||||||
|
*/
|
||||||
val id: String
|
val id: String
|
||||||
get() = Build.ID
|
get() = Build.ID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示 ID
|
||||||
|
* 用于向用户显示的构建标识符
|
||||||
|
*/
|
||||||
val display: String
|
val display: String
|
||||||
get() = Build.DISPLAY
|
get() = Build.DISPLAY
|
||||||
|
|
||||||
|
// ==================== Android 版本信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android 版本号
|
||||||
|
* 示例: "13", "12", "11" 等
|
||||||
|
*/
|
||||||
val versionRelease: String
|
val versionRelease: String
|
||||||
get() = Build.VERSION.RELEASE
|
get() = Build.VERSION.RELEASE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本代号
|
||||||
|
* 开发阶段的代号,正式版通常为 "REL"
|
||||||
|
*/
|
||||||
val versionCodename: String
|
val versionCodename: String
|
||||||
get() = Build.VERSION.CODENAME
|
get() = Build.VERSION.CODENAME
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDK 版本号(API Level)
|
||||||
|
* 示例: 33 (Android 13), 31 (Android 12), 30 (Android 11)
|
||||||
|
*/
|
||||||
val versionSdkInt: Int
|
val versionSdkInt: Int
|
||||||
get() = Build.VERSION.SDK_INT
|
get() = Build.VERSION.SDK_INT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览版 SDK 版本号
|
||||||
|
* 如果是预览版返回非 0 值,正式版返回 0
|
||||||
|
*/
|
||||||
val versionPreviewSdkInt: Int
|
val versionPreviewSdkInt: Int
|
||||||
get() = Build.VERSION.PREVIEW_SDK_INT
|
get() = Build.VERSION.PREVIEW_SDK_INT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全补丁级别
|
||||||
|
* 格式: YYYY-MM-DD
|
||||||
|
* 示例: "2023-12-05"
|
||||||
|
*/
|
||||||
val securityPatch: String
|
val securityPatch: String
|
||||||
get() = Build.VERSION.SECURITY_PATCH
|
get() = Build.VERSION.SECURITY_PATCH
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础操作系统
|
||||||
|
* 通常为空,除非是基于其他版本构建
|
||||||
|
*/
|
||||||
val baseOs: String
|
val baseOs: String
|
||||||
get() = Build.VERSION.BASE_OS
|
get() = Build.VERSION.BASE_OS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增量版本
|
||||||
|
* 表示在同一版本号下的递增更新
|
||||||
|
*/
|
||||||
val incremental: String
|
val incremental: String
|
||||||
get() = Build.VERSION.INCREMENTAL
|
get() = Build.VERSION.INCREMENTAL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号或代号
|
||||||
|
* Android 11+ 可用
|
||||||
|
* 如果是正式版返回版本号,预览版返回代号
|
||||||
|
*/
|
||||||
val releaseOrCodename: String?
|
val releaseOrCodename: String?
|
||||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Build.VERSION.RELEASE_OR_CODENAME else null
|
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
|
val mediaPerformanceClass: Int
|
||||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Build.VERSION.MEDIA_PERFORMANCE_CLASS else 0
|
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Build.VERSION.MEDIA_PERFORMANCE_CLASS else 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本显示字符串
|
||||||
|
* Android 13+ 可用
|
||||||
|
* 用于显示的完整版本号(包括预览版)
|
||||||
|
*/
|
||||||
val releaseOrPreviewDisplay: String?
|
val releaseOrPreviewDisplay: String?
|
||||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY else null
|
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?
|
val jvmName: String?
|
||||||
get() = System.getProperty("java.vm.name")
|
get() = System.getProperty("java.vm.name")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM 供应商
|
||||||
|
* 示例: "The Android Project" 或 "Oracle Corporation"
|
||||||
|
*/
|
||||||
val jvmVendor: String?
|
val jvmVendor: String?
|
||||||
get() = System.getProperty("java.vm.vendor")
|
get() = System.getProperty("java.vm.vendor")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM 版本
|
||||||
|
* 示例: "2.1.0"
|
||||||
|
*/
|
||||||
val jvmVersion: String?
|
val jvmVersion: String?
|
||||||
get() = System.getProperty("java.vm.version")
|
get() = System.getProperty("java.vm.version")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java 类版本
|
||||||
|
* 示例: "50.0" (Java 6), "52.0" (Java 8)
|
||||||
|
*/
|
||||||
val jvmClassVersion: String?
|
val jvmClassVersion: String?
|
||||||
get() = System.getProperty("java.class.version")
|
get() = System.getProperty("java.class.version")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java 规范名称
|
||||||
|
* 示例: "Dalvik Core Library"
|
||||||
|
*/
|
||||||
val jvmSpecificationName: String?
|
val jvmSpecificationName: String?
|
||||||
get() = System.getProperty("java.specification.name")
|
get() = System.getProperty("java.specification.name")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java 规范供应商
|
||||||
|
* 示例: "The Android Project"
|
||||||
|
*/
|
||||||
val jvmSpecificationVendor: String?
|
val jvmSpecificationVendor: String?
|
||||||
get() = System.getProperty("java.specification.vendor")
|
get() = System.getProperty("java.specification.vendor")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java 规范版本
|
||||||
|
* 示例: "0.9"
|
||||||
|
*/
|
||||||
val jvmSpecificationVersion: String?
|
val jvmSpecificationVersion: String?
|
||||||
get() = System.getProperty("java.specification.version")
|
get() = System.getProperty("java.specification.version")
|
||||||
|
|
||||||
|
// ==================== 安全与内核信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 厂商安全补丁级别
|
||||||
|
* 需要 SecurityStateManagerCompat 支持
|
||||||
|
* 某些设备可能有额外的厂商安全补丁
|
||||||
|
*/
|
||||||
val vendorSecurityPatchLevel: String?
|
val vendorSecurityPatchLevel: String?
|
||||||
get() = null // globalSecurityState.getString(SecurityStateManagerCompat.KEY_VENDOR_SPL)
|
get() = null // globalSecurityState.getString(SecurityStateManagerCompat.KEY_VENDOR_SPL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内核版本
|
||||||
|
* 需要 SecurityStateManagerCompat 支持
|
||||||
|
* 示例: "5.10.157"
|
||||||
|
*/
|
||||||
val kernelVersion: String?
|
val kernelVersion: String?
|
||||||
get() = null // globalSecurityState.getString(SecurityStateManagerCompat.KEY_KERNEL_VERSION)
|
get() = null // globalSecurityState.getString(SecurityStateManagerCompat.KEY_KERNEL_VERSION)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整内核版本
|
||||||
|
* 包含内核版本和编译信息
|
||||||
|
* 示例: "5.10.157-android13-4-00001-gf0123456789a-ab9876543"
|
||||||
|
*/
|
||||||
val kernelCompleteVersion: String?
|
val kernelCompleteVersion: String?
|
||||||
get() = System.getProperty("os.version")
|
get() = System.getProperty("os.version")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootloader 版本
|
||||||
|
* 设备的引导加载程序版本
|
||||||
|
*/
|
||||||
val bootloaderVersion: String
|
val bootloaderVersion: String
|
||||||
get() = Build.BOOTLOADER
|
get() = Build.BOOTLOADER
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基带版本(无线电版本)
|
||||||
|
* 移动网络基带固件版本
|
||||||
|
*/
|
||||||
val radioVersion: String?
|
val radioVersion: String?
|
||||||
get() = Build.getRadioVersion()
|
get() = Build.getRadioVersion()
|
||||||
|
|
||||||
|
// ==================== 分区信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指纹分区信息列表
|
||||||
|
* Android 10+ 可用
|
||||||
|
* 包含系统各分区的名称和指纹信息
|
||||||
|
*
|
||||||
|
* 常见分区:
|
||||||
|
* - system: 系统分区
|
||||||
|
* - vendor: 厂商分区
|
||||||
|
* - product: 产品分区
|
||||||
|
* - odm: ODM(原始设计制造商)分区
|
||||||
|
* - system_ext: 系统扩展分区
|
||||||
|
*/
|
||||||
val fingerprintedPartitions: List<PartitionInfo>
|
val fingerprintedPartitions: List<PartitionInfo>
|
||||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
Build.getFingerprintedPartitions().map { PartitionInfo(it.name, it.fingerprint) }
|
Build.getFingerprintedPartitions().map { PartitionInfo(it.name, it.fingerprint) }
|
||||||
@ -107,5 +263,72 @@ class BuildInfo(private val context: Context) {
|
|||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分区信息数据类
|
||||||
|
* @property name 分区名称
|
||||||
|
* @property fingerprint 分区指纹(构建标识)
|
||||||
|
*/
|
||||||
data class PartitionInfo(val name: String, val fingerprint: String)
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,25 +8,111 @@ import android.hardware.camera2.CameraMetadata
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.util.Range
|
||||||
|
import android.util.SizeF
|
||||||
import com.xyzshell.andinfo.R
|
import com.xyzshell.andinfo.R
|
||||||
import com.xyzshell.andinfo.libs.camera.models.CameraCapability
|
import com.xyzshell.andinfo.libs.camera.models.CameraCapability
|
||||||
import com.xyzshell.andinfo.libs.camera.models.CameraFacing
|
import com.xyzshell.andinfo.libs.camera.models.CameraFacing
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 摄像头信息工具类
|
||||||
|
* 提供摄像头硬件参数、特性、功能列表等详细信息
|
||||||
|
* 需要权限: CAMERA
|
||||||
|
*/
|
||||||
class CameraInfo(private val context: Context) {
|
class CameraInfo(private val context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 摄像头管理器
|
||||||
|
*/
|
||||||
private val cameraManager: CameraManager? = context.getSystemService(
|
private val cameraManager: CameraManager? = context.getSystemService(
|
||||||
CameraManager::class.java
|
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<Int>?, // ISO感光范围
|
||||||
|
val exposureTimeRange: Range<Long>?, // 曝光时间范围(纳秒)
|
||||||
|
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<String>?, // 面部检测模式列表
|
||||||
|
|
||||||
|
// 功能列表
|
||||||
|
val capabilities: List<CameraCapability>, // 摄像头能力
|
||||||
|
val supportedFeatures: List<String>, // 支持的功能(HDR、RAW等)
|
||||||
|
|
||||||
|
// 模式列表
|
||||||
|
val autoExposureModes: List<String>?, // 自动曝光模式
|
||||||
|
val autoFocusModes: List<String>?, // 自动对焦模式
|
||||||
|
val whiteBalanceModes: List<String>?, // 白平衡模式
|
||||||
|
val sceneModes: List<String>?, // 场景模式
|
||||||
|
val colorEffects: List<String>?, // 色彩效果
|
||||||
|
|
||||||
|
// 视频能力
|
||||||
|
val maxFrameRate: Int?, // 最大帧率
|
||||||
|
val supportsHighSpeedVideo: Boolean, // 是否支持高速视频
|
||||||
|
val supports4K: Boolean, // 是否支持4K录制
|
||||||
|
val supports8K: Boolean // 是否支持8K录制
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有摄像头ID列表
|
||||||
|
*/
|
||||||
val cameraIds: List<String>
|
val cameraIds: List<String>
|
||||||
get() = cameraManager?.cameraIdList?.toList() ?: emptyList()
|
get() = cameraManager?.cameraIdList?.toList() ?: emptyList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定摄像头的特性对象
|
||||||
|
* @param cameraId 摄像头ID
|
||||||
|
* @return CameraCharacteristics 对象
|
||||||
|
*/
|
||||||
fun getCameraCharacteristics(cameraId: String): CameraCharacteristics? {
|
fun getCameraCharacteristics(cameraId: String): CameraCharacteristics? {
|
||||||
return cameraManager?.getCameraCharacteristics(cameraId)
|
return cameraManager?.getCameraCharacteristics(cameraId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听摄像头可用性变化的 Flow
|
||||||
|
*/
|
||||||
fun cameraIdsFlow() = cameraManager?.let { manager ->
|
fun cameraIdsFlow() = cameraManager?.let { manager ->
|
||||||
callbackFlow {
|
callbackFlow {
|
||||||
val onCameraIdUpdated = {
|
val onCameraIdUpdated = {
|
||||||
@ -55,6 +141,12 @@ class CameraInfo(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 基本信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取摄像头朝向
|
||||||
|
* @return 前置/后置/外置
|
||||||
|
*/
|
||||||
fun getCameraFacing(characteristics: CameraCharacteristics): CameraFacing? {
|
fun getCameraFacing(characteristics: CameraCharacteristics): CameraFacing? {
|
||||||
return characteristics.get(CameraCharacteristics.LENS_FACING)?.let {
|
return characteristics.get(CameraCharacteristics.LENS_FACING)?.let {
|
||||||
when (it) {
|
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? {
|
fun getPixelArraySize(characteristics: CameraCharacteristics): String? {
|
||||||
return characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)?.toString()
|
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? {
|
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? {
|
fun getAvailableApertures(characteristics: CameraCharacteristics): FloatArray? {
|
||||||
return characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)
|
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<Int>? {
|
||||||
|
return characteristics.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取曝光时间范围(纳秒)
|
||||||
|
*/
|
||||||
|
fun getExposureTimeRange(characteristics: CameraCharacteristics): Range<Long>? {
|
||||||
|
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<String>? {
|
||||||
|
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<CameraCapability> {
|
fun getAvailableCapabilities(characteristics: CameraCharacteristics): List<CameraCapability> {
|
||||||
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
|
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
|
||||||
return capabilities?.toList()?.mapNotNull { capability ->
|
return capabilities?.toList()?.mapNotNull { capability ->
|
||||||
@ -111,4 +472,235 @@ class CameraInfo(private val context: Context) {
|
|||||||
}
|
}
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支持的功能列表(人类可读)
|
||||||
|
*/
|
||||||
|
fun getSupportedFeatures(characteristics: CameraCharacteristics): List<String> {
|
||||||
|
val features = mutableListOf<String>()
|
||||||
|
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<String>? {
|
||||||
|
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<String>? {
|
||||||
|
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<String>? {
|
||||||
|
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<String>? {
|
||||||
|
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<String>? {
|
||||||
|
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<CameraDetails> {
|
||||||
|
return cameraIds.mapNotNull { getCameraDetails(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.xyzshell.andinfo.libs
|
package com.xyzshell.andinfo.libs
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import com.xyzshell.andinfo.libs.cpu.models.Cache
|
import com.xyzshell.andinfo.libs.cpu.models.Cache
|
||||||
import com.xyzshell.andinfo.libs.cpu.models.Cluster
|
import com.xyzshell.andinfo.libs.cpu.models.Cluster
|
||||||
import com.xyzshell.andinfo.libs.cpu.models.Core
|
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.Processor
|
||||||
import com.xyzshell.andinfo.libs.cpu.models.UarchInfo
|
import com.xyzshell.andinfo.libs.cpu.models.UarchInfo
|
||||||
import com.xyzshell.andinfo.libs.cpu.utils.CpuInfoUtils
|
import com.xyzshell.andinfo.libs.cpu.utils.CpuInfoUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CPU 信息类
|
||||||
|
* 用于获取设备处理器的详细信息,包括核心数、频率、架构、缓存等
|
||||||
|
*/
|
||||||
class CpuInfo {
|
class CpuInfo {
|
||||||
|
|
||||||
|
// ==================== 基础处理器信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有逻辑处理器列表
|
||||||
|
* @return 逻辑处理器列表
|
||||||
|
*/
|
||||||
val processors: List<Processor>
|
val processors: List<Processor>
|
||||||
get() = CpuInfoUtils.getProcessors()
|
get() = CpuInfoUtils.getProcessors()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有物理核心列表
|
||||||
|
* @return 物理核心列表
|
||||||
|
*/
|
||||||
val cores: List<Core>
|
val cores: List<Core>
|
||||||
get() = CpuInfoUtils.getCores()
|
get() = CpuInfoUtils.getCores()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有核心集群列表(大小核分组)
|
||||||
|
* @return 集群列表
|
||||||
|
*/
|
||||||
val clusters: List<Cluster>
|
val clusters: List<Cluster>
|
||||||
get() = CpuInfoUtils.getClusters()
|
get() = CpuInfoUtils.getClusters()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有物理封装(芯片)列表
|
||||||
|
* @return 封装列表
|
||||||
|
*/
|
||||||
val packages: List<Package>
|
val packages: List<Package>
|
||||||
get() = CpuInfoUtils.getPackages()
|
get() = CpuInfoUtils.getPackages()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有微架构信息列表
|
||||||
|
* @return 微架构信息列表
|
||||||
|
*/
|
||||||
val uarchs: List<UarchInfo>
|
val uarchs: List<UarchInfo>
|
||||||
get() = CpuInfoUtils.getUarchs()
|
get() = CpuInfoUtils.getUarchs()
|
||||||
|
|
||||||
|
// ==================== 缓存信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L1 指令缓存列表
|
||||||
|
* @return L1i 缓存列表
|
||||||
|
*/
|
||||||
val l1iCaches: List<Cache>
|
val l1iCaches: List<Cache>
|
||||||
get() = CpuInfoUtils.getL1iCaches()
|
get() = CpuInfoUtils.getL1iCaches()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L1 数据缓存列表
|
||||||
|
* @return L1d 缓存列表
|
||||||
|
*/
|
||||||
val l1dCaches: List<Cache>
|
val l1dCaches: List<Cache>
|
||||||
get() = CpuInfoUtils.getL1dCaches()
|
get() = CpuInfoUtils.getL1dCaches()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L2 缓存列表
|
||||||
|
* @return L2 缓存列表
|
||||||
|
*/
|
||||||
val l2Caches: List<Cache>
|
val l2Caches: List<Cache>
|
||||||
get() = CpuInfoUtils.getL2Caches()
|
get() = CpuInfoUtils.getL2Caches()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L3 缓存列表
|
||||||
|
* @return L3 缓存列表
|
||||||
|
*/
|
||||||
val l3Caches: List<Cache>
|
val l3Caches: List<Cache>
|
||||||
get() = CpuInfoUtils.getL3Caches()
|
get() = CpuInfoUtils.getL3Caches()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L4 缓存列表
|
||||||
|
* @return L4 缓存列表
|
||||||
|
*/
|
||||||
val l4Caches: List<Cache>
|
val l4Caches: List<Cache>
|
||||||
get() = CpuInfoUtils.getL4Caches()
|
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 {
|
} 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<String> {
|
||||||
|
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<String> {
|
||||||
|
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<Long>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有核心的详细频率信息
|
||||||
|
* @return 核心频率信息列表
|
||||||
|
*/
|
||||||
|
fun getCoreFrequencies(): List<CoreFrequencyInfo> {
|
||||||
|
val result = mutableListOf<CoreFrequencyInfo>()
|
||||||
|
|
||||||
|
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<Long> {
|
||||||
|
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<String> {
|
||||||
|
return (0 until getCoreCount()).map { getGovernor(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用的调频器列表
|
||||||
|
* @param coreId 核心 ID
|
||||||
|
* @return 可用调频器列表
|
||||||
|
*/
|
||||||
|
fun getAvailableGovernors(coreId: Int = 0): List<String> {
|
||||||
|
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<String> {
|
||||||
|
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<String, String> {
|
||||||
|
val result = mutableMapOf<String, String>()
|
||||||
|
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 {
|
fun text(): String {
|
||||||
val stringBuilder = StringBuilder()
|
val sb = StringBuilder()
|
||||||
stringBuilder.append("处理器数量: ${processors.size}\n")
|
|
||||||
processors.forEachIndexed { index, processor ->
|
// 基本信息
|
||||||
stringBuilder.append(" 处理器 ${index}: ${processor.cpuPackage?.name ?: "未知"}\n")
|
sb.append("=== CPU 基本信息 ===\n")
|
||||||
stringBuilder.append(" 核心: ${processor.core?.coreId ?: "未知"}\n")
|
sb.append("处理器名称: ${getProcessorName()}\n")
|
||||||
stringBuilder.append(" 集群: ${processor.cluster?.clusterId ?: "未知"}\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")
|
sb.append("物理核心数: ${getCoreCount()}\n")
|
||||||
cores.forEachIndexed { index, core ->
|
sb.append("逻辑处理器数: ${getProcessorCount()}\n")
|
||||||
stringBuilder.append(" 核心 ${index}: ID=${core.coreId}, 频率=${core.frequency}Hz\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 {
|
companion object {
|
||||||
|
|||||||
@ -6,11 +6,20 @@ import android.os.Build
|
|||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示信息类,用于获取设备屏幕相关信息
|
||||||
|
*/
|
||||||
class DisplayInfo(private val context: Context) {
|
class DisplayInfo(private val context: Context) {
|
||||||
|
|
||||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认显示器信息
|
||||||
|
* @return 默认显示器信息对象,如果获取失败返回 null
|
||||||
|
*/
|
||||||
fun getDefaultDisplayInfo(): DefaultDisplayInfo? {
|
fun getDefaultDisplayInfo(): DefaultDisplayInfo? {
|
||||||
val defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY) ?: return null
|
val defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY) ?: return null
|
||||||
|
|
||||||
@ -22,13 +31,24 @@ class DisplayInfo(private val context: Context) {
|
|||||||
|
|
||||||
val mode = defaultDisplay.mode
|
val mode = defaultDisplay.mode
|
||||||
val refreshRate = mode.refreshRate
|
val refreshRate = mode.refreshRate
|
||||||
|
// 计算 PPI (每英寸像素数)
|
||||||
val isHdr = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val ppi = calculatePPI(displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi)
|
||||||
defaultDisplay.hdrCapabilities?.supportedHdrTypes?.isNotEmpty() ?: false
|
// 获取支持的刷新率列表
|
||||||
|
val supportedRefreshRates = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
defaultDisplay.supportedModes.map { it.refreshRate }.distinct().sorted()
|
||||||
} else {
|
} 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) {
|
val isWideColorGamut = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
defaultDisplay.isWideColorGamut
|
defaultDisplay.isWideColorGamut
|
||||||
} else {
|
} else {
|
||||||
@ -43,22 +63,81 @@ class DisplayInfo(private val context: Context) {
|
|||||||
densityDpi = displayMetrics.densityDpi,
|
densityDpi = displayMetrics.densityDpi,
|
||||||
xdpi = displayMetrics.xdpi,
|
xdpi = displayMetrics.xdpi,
|
||||||
ydpi = displayMetrics.ydpi,
|
ydpi = displayMetrics.ydpi,
|
||||||
|
ppi = ppi,
|
||||||
refreshRate = refreshRate,
|
refreshRate = refreshRate,
|
||||||
|
supportedRefreshRates = supportedRefreshRates,
|
||||||
isHdr = isHdr,
|
isHdr = isHdr,
|
||||||
|
hdrTypes,
|
||||||
isWideColorGamut = isWideColorGamut
|
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(
|
data class DefaultDisplayInfo(
|
||||||
val id: Int,
|
val id: Int, // 显示器 ID
|
||||||
val name: String,
|
val name: String, // 显示器名称
|
||||||
val widthPixels: Int,
|
val widthPixels: Int, // 屏幕宽度(像素)
|
||||||
val heightPixels: Int,
|
val heightPixels: Int, // 屏幕高度(像素)
|
||||||
val densityDpi: Int,
|
val densityDpi: Int, // 屏幕密度 DPI
|
||||||
val xdpi: Float,
|
val xdpi: Float, // X 轴 DPI
|
||||||
val ydpi: Float,
|
val ydpi: Float, // Y 轴 DPI
|
||||||
val refreshRate: Float,
|
val ppi: Double, // 屏幕 PPI
|
||||||
val isHdr: Boolean,
|
val refreshRate: Float, // 当前刷新率(Hz)
|
||||||
val isWideColorGamut: Boolean
|
val supportedRefreshRates: List<Float>, // 支持的刷新率列表(Hz)
|
||||||
|
val isHdr: Boolean, // 是否支持 HDR
|
||||||
|
val hdrTypes: List<String>, // 支持的 HDR 类型列表
|
||||||
|
val isWideColorGamut: Boolean // 是否支持广色域
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,142 @@
|
|||||||
package com.xyzshell.andinfo.libs
|
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<Long, Long> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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<String>?, // IPv6地址列表
|
||||||
|
val supportedStandards: List<String>?, // 硬件支持的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<SimInfo> // 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<SimInfo>()
|
||||||
|
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<String> {
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
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<String> {
|
||||||
|
val list = mutableListOf<String>()
|
||||||
|
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 "未知"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,22 +5,27 @@ import android.os.Environment
|
|||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
import com.xyzshell.andinfo.libs.storage.models.EncryptionType
|
import com.xyzshell.andinfo.libs.storage.models.EncryptionType
|
||||||
import com.xyzshell.andinfo.utils.SystemProperties
|
import com.xyzshell.andinfo.utils.SystemProperties
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class StorageInfo(private val context: Context) {
|
class StorageInfo(private val context: Context) {
|
||||||
|
|
||||||
|
// 内部存储总空间
|
||||||
val internalStorageTotalSpace: Long
|
val internalStorageTotalSpace: Long
|
||||||
get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs ->
|
get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs ->
|
||||||
statFs.blockCountLong * statFs.blockSizeLong
|
statFs.blockCountLong * statFs.blockSizeLong
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内部存储可用空间
|
||||||
val internalStorageAvailableSpace: Long
|
val internalStorageAvailableSpace: Long
|
||||||
get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs ->
|
get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs ->
|
||||||
statFs.availableBlocksLong * statFs.blockSizeLong
|
statFs.availableBlocksLong * statFs.blockSizeLong
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内部存储已使用空间
|
||||||
val internalStorageUsedSpace: Long
|
val internalStorageUsedSpace: Long
|
||||||
get() = internalStorageTotalSpace - internalStorageAvailableSpace
|
get() = internalStorageTotalSpace - internalStorageAvailableSpace
|
||||||
|
|
||||||
|
// 内部存储是否加密
|
||||||
val isInternalStorageEncrypted: Boolean?
|
val isInternalStorageEncrypted: Boolean?
|
||||||
get() = when (SystemProperties.getString("ro.crypto.state", "unknown")) {
|
get() = when (SystemProperties.getString("ro.crypto.state", "unknown")) {
|
||||||
"encrypted" -> true
|
"encrypted" -> true
|
||||||
@ -28,6 +33,7 @@ class StorageInfo(private val context: Context) {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内部存储加密类型
|
||||||
val internalStorageEncryptionType: EncryptionType?
|
val internalStorageEncryptionType: EncryptionType?
|
||||||
get() = when (SystemProperties.getString("ro.crypto.type", "unknown")) {
|
get() = when (SystemProperties.getString("ro.crypto.type", "unknown")) {
|
||||||
"none" -> EncryptionType.NONE
|
"none" -> EncryptionType.NONE
|
||||||
@ -36,6 +42,58 @@ class StorageInfo(private val context: Context) {
|
|||||||
else -> null
|
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?
|
val externalStorageTotalSpace: Long?
|
||||||
get() = runCatching {
|
get() = runCatching {
|
||||||
StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs ->
|
StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs ->
|
||||||
@ -43,6 +101,7 @@ class StorageInfo(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
|
// 外部存储可用空间
|
||||||
val externalStorageAvailableSpace: Long?
|
val externalStorageAvailableSpace: Long?
|
||||||
get() = runCatching {
|
get() = runCatching {
|
||||||
StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs ->
|
StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs ->
|
||||||
@ -50,6 +109,7 @@ class StorageInfo(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
|
// 外部存储已使用空间
|
||||||
val externalStorageUsedSpace: Long?
|
val externalStorageUsedSpace: Long?
|
||||||
get() = externalStorageTotalSpace?.let { total ->
|
get() = externalStorageTotalSpace?.let { total ->
|
||||||
externalStorageAvailableSpace?.let { available ->
|
externalStorageAvailableSpace?.let { available ->
|
||||||
@ -57,36 +117,76 @@ class StorageInfo(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 外部存储是否为模拟存储
|
||||||
val isExternalStorageEmulated: Boolean
|
val isExternalStorageEmulated: Boolean
|
||||||
get() = Environment.isExternalStorageEmulated()
|
get() = Environment.isExternalStorageEmulated()
|
||||||
|
|
||||||
|
// 外部存储是否可移除
|
||||||
val isExternalStorageRemovable: Boolean
|
val isExternalStorageRemovable: Boolean
|
||||||
get() = Environment.isExternalStorageRemovable()
|
get() = Environment.isExternalStorageRemovable()
|
||||||
|
|
||||||
|
// 是否支持可更新的 APEX
|
||||||
val hasUpdatableApex: Boolean?
|
val hasUpdatableApex: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.apex.updatable")
|
get() = SystemProperties.getBoolean("ro.apex.updatable")
|
||||||
|
|
||||||
|
// 是否使用 system-as-root 分区方案
|
||||||
val usesSystemAsRoot: Boolean?
|
val usesSystemAsRoot: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.build.system_root_image")
|
get() = SystemProperties.getBoolean("ro.build.system_root_image")
|
||||||
|
|
||||||
|
// 是否使用 A/B 分区更新方案
|
||||||
val usesAb: Boolean?
|
val usesAb: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.build.ab_update")
|
get() = SystemProperties.getBoolean("ro.build.ab_update")
|
||||||
|
|
||||||
|
// A/B OTA 分区列表
|
||||||
val abOtaPartitions: Array<String>?
|
val abOtaPartitions: Array<String>?
|
||||||
get() = SystemProperties.getString("ro.product.ab_ota_partitions")?.split(",")?.toTypedArray()
|
get() = SystemProperties.getString("ro.product.ab_ota_partitions")?.split(",")?.toTypedArray()
|
||||||
|
|
||||||
|
// 是否使用动态分区
|
||||||
val usesDynamicPartitions: Boolean?
|
val usesDynamicPartitions: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions")
|
get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions")
|
||||||
|
|
||||||
|
// 是否使用改造的动态分区
|
||||||
val usesRetrofittedDynamicPartitions: Boolean?
|
val usesRetrofittedDynamicPartitions: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions_retrofit")
|
get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions_retrofit")
|
||||||
|
|
||||||
|
// 是否使用虚拟 A/B 分区
|
||||||
val usesVirtualAb: Boolean?
|
val usesVirtualAb: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.virtual_ab.enabled")
|
get() = SystemProperties.getBoolean("ro.virtual_ab.enabled")
|
||||||
|
|
||||||
|
// 是否使用改造的虚拟 A/B 分区
|
||||||
val usesRetrofittedVirtualAb: Boolean?
|
val usesRetrofittedVirtualAb: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.virtual_ab.retrofit")
|
get() = SystemProperties.getBoolean("ro.virtual_ab.retrofit")
|
||||||
|
|
||||||
|
// 虚拟 A/B 分区是否启用压缩
|
||||||
val usesCompressedVirtualAb: Boolean?
|
val usesCompressedVirtualAb: Boolean?
|
||||||
get() = SystemProperties.getBoolean("ro.virtual_ab.compression.enabled")
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 <T> toJson(obj: T): String {
|
||||||
|
return gson.toJson(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化 JSON 为对象
|
||||||
|
inline fun <reified T> fromJson(json: String): T? {
|
||||||
|
return try {
|
||||||
|
gson.fromJson(json, T::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带序列化的 POST 请求
|
||||||
|
fun <T> postObject(url: String, obj: T, callback: (String?, Exception?) -> Unit) {
|
||||||
|
val jsonData = toJson(obj)
|
||||||
|
postData(url, jsonData, callback)
|
||||||
|
}
|
||||||
|
// 带序列化和反序列化的 POST 请求
|
||||||
|
inline fun <reified T, reified R> 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<R>(response)
|
||||||
|
callback(result, null)
|
||||||
|
} else {
|
||||||
|
callback(null, Exception("Empty response"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user