diff --git a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt index 844e5a3..1cf0118 100644 --- a/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt +++ b/myphoneinfo/andinfo/src/main/java/com/xyzshell/andinfo/libs/StorageInfo.kt @@ -5,22 +5,27 @@ import android.os.Environment import android.os.StatFs import com.xyzshell.andinfo.libs.storage.models.EncryptionType import com.xyzshell.andinfo.utils.SystemProperties +import java.io.File class StorageInfo(private val context: Context) { + // 内部存储总空间 val internalStorageTotalSpace: Long get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs -> statFs.blockCountLong * statFs.blockSizeLong } + // 内部存储可用空间 val internalStorageAvailableSpace: Long get() = StatFs(Environment.getDataDirectory().absolutePath).let { statFs -> statFs.availableBlocksLong * statFs.blockSizeLong } + // 内部存储已使用空间 val internalStorageUsedSpace: Long get() = internalStorageTotalSpace - internalStorageAvailableSpace + // 内部存储是否加密 val isInternalStorageEncrypted: Boolean? get() = when (SystemProperties.getString("ro.crypto.state", "unknown")) { "encrypted" -> true @@ -28,6 +33,7 @@ class StorageInfo(private val context: Context) { else -> null } + // 内部存储加密类型 val internalStorageEncryptionType: EncryptionType? get() = when (SystemProperties.getString("ro.crypto.type", "unknown")) { "none" -> EncryptionType.NONE @@ -36,6 +42,58 @@ class StorageInfo(private val context: Context) { else -> null } + // 内部存储文件系统类型 + val internalStorageFileSystemType: String? + get() = try { + // 方法1: 读取 /proc/mounts 文件 + File("/proc/mounts").readLines() + .find { it.contains(" /data ") } + ?.split("\\s+".toRegex()) + ?.getOrNull(2) + ?: run { + // 方法2: 使用 StatFs (Android 8.0+) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + val statFs = StatFs("/data") + statFs.javaClass.getMethod("getFileSystemName").invoke(statFs) as? String + } else { + null + } + } + } catch (e: Exception) { + null + } + + // /data 目录总大小 + val dataDirectoryTotalSpace: Long + get() = StatFs("/data").let { statFs -> + statFs.blockCountLong * statFs.blockSizeLong + } + + // /data 目录已使用空间 + val dataDirectoryUsedSpace: Long + get() = StatFs("/data").let { statFs -> + val total = statFs.blockCountLong * statFs.blockSizeLong + val available = statFs.availableBlocksLong * statFs.blockSizeLong + total - available + } + + // 应用程序和数据占用大小 + val applicationsAndDataSize: Long + get() = try { + val dataDir = File("/data/data") + val appDir = File("/data/app") + calculateDirectorySize(dataDir) + calculateDirectorySize(appDir) + } catch (e: Exception) { + 0L + } + + // 系统占用大小 + val systemSize: Long + get() = StatFs(Environment.getRootDirectory().absolutePath).let { statFs -> + statFs.blockCountLong * statFs.blockSizeLong + } + + // 外部存储总空间 val externalStorageTotalSpace: Long? get() = runCatching { StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs -> @@ -43,6 +101,7 @@ class StorageInfo(private val context: Context) { } }.getOrNull() + // 外部存储可用空间 val externalStorageAvailableSpace: Long? get() = runCatching { StatFs(Environment.getExternalStorageDirectory().absolutePath).let { statFs -> @@ -50,6 +109,7 @@ class StorageInfo(private val context: Context) { } }.getOrNull() + // 外部存储已使用空间 val externalStorageUsedSpace: Long? get() = externalStorageTotalSpace?.let { total -> externalStorageAvailableSpace?.let { available -> @@ -57,36 +117,76 @@ class StorageInfo(private val context: Context) { } } + // 外部存储是否为模拟存储 val isExternalStorageEmulated: Boolean get() = Environment.isExternalStorageEmulated() + // 外部存储是否可移除 val isExternalStorageRemovable: Boolean get() = Environment.isExternalStorageRemovable() + // 是否支持可更新的 APEX val hasUpdatableApex: Boolean? get() = SystemProperties.getBoolean("ro.apex.updatable") + // 是否使用 system-as-root 分区方案 val usesSystemAsRoot: Boolean? get() = SystemProperties.getBoolean("ro.build.system_root_image") + // 是否使用 A/B 分区更新方案 val usesAb: Boolean? get() = SystemProperties.getBoolean("ro.build.ab_update") + // A/B OTA 分区列表 val abOtaPartitions: Array? get() = SystemProperties.getString("ro.product.ab_ota_partitions")?.split(",")?.toTypedArray() + // 是否使用动态分区 val usesDynamicPartitions: Boolean? get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions") + // 是否使用改造的动态分区 val usesRetrofittedDynamicPartitions: Boolean? get() = SystemProperties.getBoolean("ro.boot.dynamic_partitions_retrofit") + // 是否使用虚拟 A/B 分区 val usesVirtualAb: Boolean? get() = SystemProperties.getBoolean("ro.virtual_ab.enabled") + // 是否使用改造的虚拟 A/B 分区 val usesRetrofittedVirtualAb: Boolean? get() = SystemProperties.getBoolean("ro.virtual_ab.retrofit") + // 虚拟 A/B 分区是否启用压缩 val usesCompressedVirtualAb: Boolean? get() = SystemProperties.getBoolean("ro.virtual_ab.compression.enabled") + + // 计算目录大小(递归) + private fun calculateDirectorySize(directory: File): Long { + var size = 0L + try { + if (directory.exists() && directory.isDirectory) { + directory.listFiles()?.forEach { file -> + size += if (file.isDirectory) { + calculateDirectorySize(file) + } else { + file.length() + } + } + } + } catch (e: Exception) { + // 可能因权限问题无法访问某些目录 + } + return size + } + + // 格式化存储大小 + fun formatBytes(bytes: Long): String { + return when { + bytes < 1024 -> "$bytes B" + bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0) + bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024)) + else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024)) + } + } }