Compare commits
2 Commits
d1fa83d15c
...
5239088041
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5239088041 | ||
|
|
a43fcad558 |
@ -7,7 +7,7 @@ apiVersion = "82"
|
||||
datastore = "1.1.7"
|
||||
datastorePreferences = "1.1.7"
|
||||
googleSymbolProcessingApi = "2.0.0-1.0.24"
|
||||
gson = "2.13.1"
|
||||
gson = "2.13.2"
|
||||
jsonVersion = "20250517"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.16.0"
|
||||
|
||||
@ -50,6 +50,8 @@ dependencies {
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
// implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7")
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.squareup.okhttp)
|
||||
implementation(libs.gson)
|
||||
// implementation("androidx.datastore:datastore-rxjava3:1.1.7")
|
||||
implementation("androidx.security:security-state:1.0.0-alpha04")
|
||||
testImplementation(libs.junit)
|
||||
|
||||
@ -35,11 +35,6 @@ class BluetoothInfo(private val context: Context) {
|
||||
|
||||
val requiredPermissions: Array<String>
|
||||
get() = buildList {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
} else {
|
||||
add(Manifest.permission.BLUETOOTH)
|
||||
add(Manifest.permission.BLUETOOTH_ADMIN)
|
||||
}
|
||||
add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
@ -1,4 +1,141 @@
|
||||
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 {
|
||||
|
||||
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,4 @@
|
||||
package com.xyzshell.andinfo.libs
|
||||
|
||||
class NetworkInfo {
|
||||
}
|
||||
@ -5,22 +5,27 @@ import android.os.Environment
|
||||
import android.os.StatFs
|
||||
import com.xyzshell.andinfo.libs.storage.models.EncryptionType
|
||||
import com.xyzshell.andinfo.utils.SystemProperties
|
||||
import java.io.File
|
||||
|
||||
class StorageInfo(private val context: Context) {
|
||||
|
||||
// 内部存储总空间
|
||||
val internalStorageTotalSpace: Long
|
||||
get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs ->
|
||||
statFs.blockCountLong * statFs.blockSizeLong
|
||||
}
|
||||
|
||||
// 内部存储可用空间
|
||||
val internalStorageAvailableSpace: Long
|
||||
get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs ->
|
||||
statFs.availableBlocksLong * statFs.blockSizeLong
|
||||
}
|
||||
|
||||
// 内部存储已使用空间
|
||||
val internalStorageUsedSpace: Long
|
||||
get() = internalStorageTotalSpace - internalStorageAvailableSpace
|
||||
|
||||
// 内部存储是否加密
|
||||
val isInternalStorageEncrypted: Boolean?
|
||||
get() = when (SystemProperties.getString("ro.crypto.state", "unknown")) {
|
||||
"encrypted" -> true
|
||||
@ -28,6 +33,7 @@ class StorageInfo(private val context: Context) {
|
||||
else -> null
|
||||
}
|
||||
|
||||
// 内部存储加密类型
|
||||
val internalStorageEncryptionType: EncryptionType?
|
||||
get() = when (SystemProperties.getString("ro.crypto.type", "unknown")) {
|
||||
"none" -> EncryptionType.NONE
|
||||
@ -36,6 +42,58 @@ class StorageInfo(private val context: Context) {
|
||||
else -> null
|
||||
}
|
||||
|
||||
// 内部存储文件系统类型
|
||||
val internalStorageFileSystemType: String?
|
||||
get() = try {
|
||||
// 方法1: 读取 /proc/mounts 文件
|
||||
File("/proc/mounts").readLines()
|
||||
.find { it.contains(" /data ") }
|
||||
?.split("\\s+".toRegex())
|
||||
?.getOrNull(2)
|
||||
?: run {
|
||||
// 方法2: 使用 StatFs (Android 8.0+)
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
val statFs = StatFs("/data")
|
||||
statFs.javaClass.getMethod("getFileSystemName").invoke(statFs) as? String
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
// /data 目录总大小
|
||||
val dataDirectoryTotalSpace: Long
|
||||
get() = StatFs("/data").let { statFs ->
|
||||
statFs.blockCountLong * statFs.blockSizeLong
|
||||
}
|
||||
|
||||
// /data 目录已使用空间
|
||||
val dataDirectoryUsedSpace: Long
|
||||
get() = StatFs("/data").let { statFs ->
|
||||
val total = statFs.blockCountLong * statFs.blockSizeLong
|
||||
val available = statFs.availableBlocksLong * statFs.blockSizeLong
|
||||
total - available
|
||||
}
|
||||
|
||||
// 应用程序和数据占用大小
|
||||
val applicationsAndDataSize: Long
|
||||
get() = try {
|
||||
val dataDir = File("/data/data")
|
||||
val appDir = File("/data/app")
|
||||
calculateDirectorySize(dataDir) + calculateDirectorySize(appDir)
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
|
||||
// 系统占用大小
|
||||
val systemSize: Long
|
||||
get() = StatFs(Environment.getRootDirectory().absolutePath).let { statFs ->
|
||||
statFs.blockCountLong * statFs.blockSizeLong
|
||||
}
|
||||
|
||||
// 外部存储总空间
|
||||
val externalStorageTotalSpace: Long?
|
||||
get() = runCatching {
|
||||
StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs ->
|
||||
@ -43,6 +101,7 @@ class StorageInfo(private val context: Context) {
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
// 外部存储可用空间
|
||||
val externalStorageAvailableSpace: Long?
|
||||
get() = runCatching {
|
||||
StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs ->
|
||||
@ -50,6 +109,7 @@ class StorageInfo(private val context: Context) {
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
// 外部存储已使用空间
|
||||
val externalStorageUsedSpace: Long?
|
||||
get() = externalStorageTotalSpace?.let { total ->
|
||||
externalStorageAvailableSpace?.let { available ->
|
||||
@ -57,36 +117,76 @@ class StorageInfo(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 外部存储是否为模拟存储
|
||||
val isExternalStorageEmulated: Boolean
|
||||
get() = Environment.isExternalStorageEmulated()
|
||||
|
||||
// 外部存储是否可移除
|
||||
val isExternalStorageRemovable: Boolean
|
||||
get() = Environment.isExternalStorageRemovable()
|
||||
|
||||
// 是否支持可更新的 APEX
|
||||
val hasUpdatableApex: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.apex.updatable")
|
||||
|
||||
// 是否使用 system-as-root 分区方案
|
||||
val usesSystemAsRoot: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.build.system_root_image")
|
||||
|
||||
// 是否使用 A/B 分区更新方案
|
||||
val usesAb: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.build.ab_update")
|
||||
|
||||
// A/B OTA 分区列表
|
||||
val abOtaPartitions: Array<String>?
|
||||
get() = SystemProperties.getString("ro.product.ab_ota_partitions")?.split(",")?.toTypedArray()
|
||||
|
||||
// 是否使用动态分区
|
||||
val usesDynamicPartitions: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions")
|
||||
|
||||
// 是否使用改造的动态分区
|
||||
val usesRetrofittedDynamicPartitions: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions_retrofit")
|
||||
|
||||
// 是否使用虚拟 A/B 分区
|
||||
val usesVirtualAb: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.virtual_ab.enabled")
|
||||
|
||||
// 是否使用改造的虚拟 A/B 分区
|
||||
val usesRetrofittedVirtualAb: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.virtual_ab.retrofit")
|
||||
|
||||
// 虚拟 A/B 分区是否启用压缩
|
||||
val usesCompressedVirtualAb: Boolean?
|
||||
get() = SystemProperties.getBoolean("ro.virtual_ab.compression.enabled")
|
||||
|
||||
// 计算目录大小(递归)
|
||||
private fun calculateDirectorySize(directory: File): Long {
|
||||
var size = 0L
|
||||
try {
|
||||
if (directory.exists() && directory.isDirectory) {
|
||||
directory.listFiles()?.forEach { file ->
|
||||
size += if (file.isDirectory) {
|
||||
calculateDirectorySize(file)
|
||||
} else {
|
||||
file.length()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 可能因权限问题无法访问某些目录
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// 格式化存储大小
|
||||
fun formatBytes(bytes: Long): String {
|
||||
return when {
|
||||
bytes < 1024 -> "$bytes B"
|
||||
bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0)
|
||||
bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024))
|
||||
else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user