5

How can I check if the device has low storage on Android 8 Oreo. I saw in the Android Documentation that the Intent.ACTION_DEVICE_STORAGE_LOW is deprecated in API 26.

This constant was deprecated in API level 26. if your app targets O or above, this broadcast will no longer be delivered to any BroadcastReceiver defined in your manifest. Instead, apps are strongly encouraged to use the improved getCacheDir() behavior so the system can automatically free up storage when needed. - Android Documentation

They are encouraging me use getCacheDir() instead.

But I don't understand much of it, as getCacheDir() seems to return the system cache directory path as a FILE object, which can only be used to clear cache or some such.

But I need to check whether the device is running low on device storage. I hope someone will help me in this

webo80
  • 3,365
  • 5
  • 35
  • 52
Arun K Babu
  • 113
  • 1
  • 10
  • Once getCacheDir() delivered the path to the used storage you can use that path to determine size and free space of that 'partition'. – greenapps Nov 15 '17 at 15:34
  • By 'partition' , do you mean cache partition? – Arun K Babu Nov 16 '17 at 10:43
  • Please reread my comment. And getCacheDir() delivers a directory in internal storage. – greenapps Nov 16 '17 at 10:53
  • Thank you so much for the reply! That cleared my doubt – Arun K Babu Nov 16 '17 at 13:10
  • Still, I'm wondering how I can use `getCacheDir()` to know whether or not the 'not enough storage' state is active, because for example `SyncAdapter`s won't sync in that state. – Erik Apr 12 '18 at 14:31
  • Also, [this answer](https://stackoverflow.com/a/8118832/3169029) states that the threshold is 10% by default, but can we find this threshold programmatically so we can actually calculate whether or not the threshold has been reached? – Erik Apr 13 '18 at 06:20
  • Also, why do you need to check on low device storage? Do you need to allocate space to write files? Or is something else not functioning due to the storage low state the device is in? – Erik Apr 14 '18 at 11:42

2 Answers2

10

See Android's (AndroidX work) StorageNotLowTracker implementation for an example of how to receive system broadcasts when storage becomes low or OK.

Note that this is the implementation used by AndroidX work when using a 'storage not low constraint'. It uses deprecated intent filter broadcast actions, but it still works today.

I have created a similar implementation (not shared in this answer) that can be registered, unregistered and has two callbacks: on storage low and on storage OK.

See StorageNotLowTrackerTest for an example of how to test this.


Old answer kept for reference below

As correctly stated in the question, the API 26 Intent.ACTION_DEVICE_STORAGE_LOW is deprecated and Context#getCacheDir() is advised to be used instead to free up space from you application's cache.

There are multiple problems with this (enumerated below), but first: note that it is good practice to keep cache 'reasonably small' (e.g. 1 MB), I quote:

getCacheDir()

Returns a File representing an internal directory for your app's temporary cache files. Be sure to delete each file once it is no longer needed and implement a reasonable size limit for the amount of memory you use at any given time, such as 1MB.

Caution: If the system runs low on storage, it may delete your cache files without warning.

(source)

So, there are three problems here:

  1. We should clear the cache, but it is probably already reasonably small (e.g. 1 MB), so clearing it will probably not free enough space for the free storage to become OK again (similar to the also deprecated Intent.ACTION_DEVICE_STORAGE_OK that previously could be used for this)
  2. As quoted, the cache quite possibly has already been cleared by the system, because the storage is low and the system may clear your application's cache if it so decides. Therefore, clearing it yourself possibly does not free up any storage.
  3. The documentation does not specify at all how to actually detect if the device is low on storage.

So, clearing the cache doesn't seem to help, so I won't go into the details of how to do that.

However, as per this answer, we could assume that at 10% free storage the system enters the low storage state that we want to detect. This number is Android's default, but there's little preventing a device manufacturer (or ROM developer) from changing it, according to the linked answer.

At this point, to me, this 10% is a magic number and I'd like to know if I can determine this threshold programmatically. If you know how, please edit my answer, post an answer yourself or comment on my answer.

To do this using getCacheDir(), you could use the following:

Java, from a Context (e.g. Activity):

File cacheDir = getCacheDir();
if (cacheDir.getUsableSpace() * 100 / cacheDir.getTotalSpace() <= 10) { // Alternatively, use cacheDir.getFreeSpace()
  // Handle storage low state
} else {
  // Handle storage ok state
}

Kotlin, from a Context (e.g. Activity):

if (cacheDir.usableSpace * 100 / cacheDir.totalSpace <= 10) { // Alternatively, use cacheDir.freeSpace
  // Handle storage low state
} else {
  // Handle storage ok state
}

Now, whether to use the usable space or free space, that's not entirely clear to me. The difference is described here.

Diving into the Android source I found a system service, that I cannot access in my code, that checks for low storage: DeviceStorageMonitorService. It gets its lowBytes variable from StorageManager#getStorageLowBytes, which I cannot access either. If that would be possible in some non-hacky way, that would be a way to get the low storage bytes threshold. There you see the source uses getUsableSpace(), so that's why I chose that instead of getFreeSpace() too for my code snippets.

Erik
  • 4,305
  • 3
  • 36
  • 54
0

After digging into the code of android excatlty the class that release the Low storage notification called DeviceStorageMonitorService

Here's what i found, Some phones use the sys_storage_threshold_percentage and some use sys_storage_threshold_max_bytes so to test the storage you should get the real value from the Settings.Secure using both keys and then compare between sys_storage_threshold_percentage * Total memory size of data system folder and sys_storage_threshold_max_bytes and then take the small value and compare it to the available storage space of data system folder, here's the code of how to do it

    private void checkForLowStorage() {

        long mFreeMem = getDeviceCurrentStorage();
        float deviceLowStorageThreshold = getDeviceLowStorageThreshold();
        if (mFreeMem <= deviceLowStorageThreshold) {
            Toast.makeText(this, R.string.low_storage_error_message, Toast.LENGTH_LONG).show();
            // Handle storage low state
        } else {
            // Handle storage ok state
        }
    }

    private long getDeviceCurrentStorage() {

        long mFreeMem = 0;
        try {
            StatFs mDataFileStats = new StatFs("/data");
            mDataFileStats.restat("/data");
            mFreeMem = (long) mDataFileStats.getAvailableBlocksLong() *
                    mDataFileStats.getBlockSizeLong();
        } catch (IllegalArgumentException e) {
            // use the old value of mFreeMem
        }
        return mFreeMem;
    }

    private long getDeviceLowStorageThreshold() {

        long value = Settings.Secure.getInt(
                getContentResolver(),
                "sys_storage_threshold_percentage",
                10);
        StatFs mDataFileStats = new StatFs("/data");
        long mTotalMemory = ((long) mDataFileStats.getBlockCountLong() *
                mDataFileStats.getBlockSizeLong()) / 100L;
        value *= mTotalMemory;
        long maxValue = Settings.Secure.getInt(
                getContentResolver(),
                "sys_storage_threshold_max_bytes",
                500*1024*1024);
        return Math.min(value, maxValue);
    }

I'm still testing it, Dunno if it's not going to work on some devices

Farido mastr
  • 464
  • 5
  • 12