Androidデバイスのストレージ容量を取得する


# アプリでストレージの全体容量を取得したい
ひょんなことで、内部ストレージの全体容量と空き容量を取得する必要性が出てきました
先行調査から「android.os.StatFsを使えばいけるんじゃね？」とのこと

https://developer.android.com/reference/android/os/StatFs#getTotalBytes()

確かに行けそう…
まぁどうやるかもよくわからんし、他サイトも調べてみると

https://zenn.dev/no4_dev/articles/bb7af37d5b44074d29bf-2

まさにな記事も発見！
さらに調べてみると上の記事でやっている
```kotlin
      val statFs = StatFs(path)
      // 総容量
      val totalSpase: Long = statFs.blockCountLong * statFs.blockSizeLong / 1024L / 1024L
      // 空き容量
      val freeSpase: Long = statFs.availableBlocksLong * statFs.blockSizeLong / 1024L / 1024L
```
というコードは
```kotlin
      val statFs = StatFs(path)
      // 総容量
      val totalSpase: Long = statFs.getTotalBytes / 1024L / 1024L
      // 空き容量
      val freeSpase: Long = statFs.getFreeBytes / 1024L / 1024L
```
とやっていることは同じであるようだった（参考サイトどっかいってしまった😭）

さて、そうなればやることはすごく簡単じゃないか！
ということでコードをポチポチ…
```kotlin
class MainActivity : AppCompatActivity() {

    //内部ストレージの全体容量
    fun getAllStorage(context: Context): Long {
        val statFs = StatFs(context.filesDir.absolutePath)
        val totalBytes = statFs.totalBytes
        return totalBytes
    }

    //内部ストレージの空き容量
    fun getFreeStorage(context: Context): Long {
        val statFs = StatFs(context.filesDir.absolutePath)
        val availableBytes = statFs.availableBytes
        return availableBytes
    }

    // バイト単位のサイズを読みやすい形式に変換するヘルパー関数
    private fun formatSize(size: Long): String {
        if (size <= 0) return "0 B"
        
        val units = arrayOf("B", "KB", "MB", "GB", "TB")
        var unitIndex = 0
        var sizeInDouble = size.toDouble()

        while (sizeInDouble >= 1024 && unitIndex < units.size - 1) {
            sizeInDouble /= 1024.0
            unitIndex++
        }
        return String.format("%.2f %s", sizeInDouble, units[unitIndex])
    }

    @SuppressLint("DiscouragedPrivateApi")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.i("MainActivity", "onCreate")
    
        val totalSpace: Long = getAllStorage(this)
        val freeSpace: Long = getFreeStorage(this)

        Log.d("MainActivity", "Total space:" + formatSize(totalSpase))
        Log.d("MainActivity", "Free space:" + formatSize(freeSpace))
    }
}
```

はい！これでバッチリ！！！……だと思うじゃないですか
これが苦悩の始まりでした

## ストレージサイズが違う
そうして出てきたサイズはこちら

```
MainActivity            com.example.storagesample            I  onCreate
MainActivity            com.example.storagesample            D  Total space:461.29 GB
MainActivity            com.example.storagesample            D  Free space:317.18 GB
```
そしてスマホの設定アプリで確認したスクショ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699725/874e30ef-03ec-0597-6694-cb7499bf32fb.png" width="300" />
<br>
<br>
んでそれらをまとめた表が↓（小数点第一位を四捨五入）

|  | 全体容量 | 空き容量 |
|:-----------|------------:|:------------:|
| 取得した値       | 461 GB        | 317 GB         |
| 設定アプリの値   | 512 GB      | 341 GB       |


う〜ん
ぜんっっっっぜん違うな！なんだこりゃ

# 原因について考えよう

こういう時はひとまずリファレンスを見直す…！

https://developer.android.com/reference/android/os/StatFs#getTotalBytes()

う〜ん言及なし！
強いていうなら”システムファイルがサポートしてる”といったところでしょうか


https://stackoverflow.com/questions/43004556/how-to-get-exact-capacity-from-storage

続いてStackOverFlowにあたってみると、それっぽいのが…！!

先ほどのコードを変更して
```kotlin
    //内部ストレージの全体容量
    fun getInternalAllStorageVolume(): Long {
        val statFs = StatFs(context.filesDir.absolutePath)
        val storagemanager = getSystemService(StorageManager::class.java)
        val path = storagemanager.storageVolumes[0].directory?.absolutePath
        Log.d (TAG, "statFs: $path")
        val totalBytes = statFs.totalBytes
        return totalBytes
    }
```
としてみると

```
ContentValues           com.example.storagesample            D  statFs: /storage/emulated/0
```
といったログが取れる
これが参照しているストレージのパス。
/storage/emulated/の値をとっていることがわかったので、
`adb shell df -h`
でROMの容量を見てみると

```
/dev/fuse         461G 144G  317G  32% /storage/emulated
```
と出る
つまり取得した値自体は間違ってなくて、一つのパーティション（？）のみを見てたからダメだったわけだ

じゃあ全部の場所を示してやればうまくいくってことかー！
よーし！
```
Filesystem        Size Used Avail Use% Mounted on
/dev/block/dm-10  3.6G 3.6G     0 100% /
tmpfs             7.4G 2.1M  7.4G   1% /dev
tmpfs             7.4G    0  7.4G   0% /mnt
/dev/block/dm-11  405M 404M     0 100% /system_ext
/dev/block/dm-12  1.7G 1.6G     0 100% /product
/dev/block/dm-13  1.6G 1.6G     0 100% /vendor
/dev/block/dm-14   62M  62M     0 100% /vendor_dlkm
/dev/block/dm-16  1.3M 1.3M     0 100% /odm
tmpfs             7.4G  96K  7.4G   1% /apex
/dev/block/sda9   372M  64M  300M  18% /asdf
----下にめっちゃ続くーーーーーーーーーーーーーーーーー
```
無理だなこれ…
何よりもいろんなデバイスに入ること想定したら、全てのデバイスに通用するやり口があるのかもわからん。実際、上記のStackOverFlowの回答者のやり方は、俺のデバイスではうまくいかず481GBになった。

There is no means of doing that, except perhaps on a rooted device.
とかも書かれてるし無理なのかなー

## 望んでいた取得方法

そんな感じで色々ためしつつ調べてを繰り返してたら、↓を発見

https://stackoverflow.com/questions/53057976/get-android-device-storage-capacity

これによると[android.os.StatFs](https://developer.android.com/reference/android/os/StatFs)ではなく、[android.app.usage.StorageStatsManager](https://developer.android.com/reference/android/app/usage/StorageStatsManager)を使えばうまくいくよとのこと…！

リファレンスみてみると
![スクリーンショット 2024-09-12 20.39.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699725/f7ac1c33-9925-32b3-84a2-12fe50619124.png)
わー！これですこれです！
僕が知りたかった内容です！
<br>
ということでまたまたコードをポチポチして
```kotlin
class MainActivity : AppCompatActivity() {

    //内部ストレージの全体容量
    fun getAllStorage(context: Context): Long {
        val storage:StorageStatsManager = context.getSystemService(StorageStatsManager::class.java)
        try {
            val totalBytes = storage.getTotalBytes(StorageManager.UUID_DEFAULT)
            return  totalBytes
        }catch (e: IOException) {
            e.printStackTrace()
            return 0
        }
    }

    //内部ストレージの空き容量
    fun getFreeStorage(context: Context): Long {
        val storage:StorageStatsManager = context.getSystemService(StorageStatsManager::class.java)
        try {
            val totalBytes = storage.getFreeBytes(StorageManager.UUID_DEFAULT)
            return  totalBytes
        }catch (e: IOException) {
            e.printStackTrace()
            return 0
        }
    }

    // バイト単位のサイズを読みやすい形式に変換するヘルパー関数
    private fun formatSize(size: Long): String {
        if (size <= 0) return "0 B"
        
        val units = arrayOf("B", "KB", "MB", "GB", "TB")
        var unitIndex = 0
        var sizeInDouble = size.toDouble()

        while (sizeInDouble >= 1024 && unitIndex < units.size - 1) {
            sizeInDouble /= 1024.0
            unitIndex++
        }
        return String.format("%.2f %s", sizeInDouble, units[unitIndex])
    }

    @SuppressLint("DiscouragedPrivateApi")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.i("MainActivity", "onCreate")
    
        val totalSpace: Long = getAllStorage(this)
        val freeSpace: Long = getFreeStorage(this)

        Log.d("MainActivity", "Total space:" + formatSize(totalSpace))
        Log.d("MainActivity", "Free space:" + formatSize(freeSpace))
    }
}
```
```
MainActivity            com.example.storagesample            I  onCreate
MainActivity            com.example.storagesample            D  Total space:476.84 GB
MainActivity            com.example.storagesample            D  Free space:317.30 GB
```

???数値が変だぞ
なんでだー！

https://stackoverflow.com/questions/56285992/statfs-methods-gettotalbytes-and-getfreebytes-not-returning-correct-values

↑の記事によるとアプリは1KBを1000Bで計算してるってこと…？
実は「1000Bで計算してるんやでー」って記述、別のブログでも度々見かけてた
そこで`formatSize`の値を1024から1000に変えてみると

```
MainActivity            com.example.storagesample            D  Total space:512.00 GB
MainActivity            com.example.storagesample            D  Free space:340.92 GB
```

おお！設定アプリで見られる値と同じ！

これにて成功！


でも何で設定アプリでは1KBを1000Bとしてるのか。
また、「設定アプリでは１KBを1000Bとしている」と言える根拠となるコードも見てないので、おいおい探していきたい
**（知っている方がいらしゃったら教えて欲しい！）**


--------
twitter:

https://twitter.com/IKent1mm