Compare commits

...

2 Commits

Author SHA1 Message Date
xsean
5239088041 存储信息修改 2025-12-23 11:26:42 +08:00
xsean
a43fcad558 add MemInfo 2025-12-23 11:00:09 +08:00
7 changed files with 339 additions and 7 deletions

View File

@ -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"

View File

@ -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)

View File

@ -35,11 +35,6 @@ class BluetoothInfo(private val context: Context) {
val requiredPermissions: Array<String> val requiredPermissions: Array<String>
get() = buildList { get() = buildList {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { add(Manifest.permission.BLUETOOTH_CONNECT)
add(Manifest.permission.BLUETOOTH_CONNECT)
} else {
add(Manifest.permission.BLUETOOTH)
add(Manifest.permission.BLUETOOTH_ADMIN)
}
}.toTypedArray() }.toTypedArray()
} }

View File

@ -1,4 +1,141 @@
package com.xyzshell.andinfo.libs package com.xyzshell.andinfo.libs
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 { class MemInfo {
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))
}
}
} }

View File

@ -0,0 +1,4 @@
package com.xyzshell.andinfo.libs
class NetworkInfo {
}

View File

@ -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))
}
}
} }

View File

@ -0,0 +1,94 @@
package com.xyzshell.andinfo.utils
import com.google.gson.Gson
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
class WebService {
private val client = OkHttpClient()
val gson = Gson()
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"))
}
}
}
}