Controlling the Behavior

Fortunately, for Android 10 at least, your app has control over whether it has traditional (“legacy”) external storage access or has “filtered” access.

Opting Out… For Now

To stick with legacy external storage, even on Android 10 devices, add android:requestLegacyExternalStorage="true" to your <application> element in your manifest.

With that in place, everything will work as it did in Android 4.4 through 9.0.

Opting In

Conversely, android:requestLegacyExternalStorage="false" opts into the “filtered” behavior. This works regardless of targetSdkVersion, so even if your targetSdkVersion is 28 or older, you can see how your app behaves when it is using scoped storage.

If you want, you can have android:requestLegacyExternalStorage be controlled by a bool resource value. The StorageExplorer sample module in the book’s sample project does this:

  <application
    android:name=".KoinApp"
    android:allowBackup="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    tools:ignore="GoogleAppIndexingWarning"
    android:requestLegacyExternalStorage="@bool/useLegacy">

The module has three flavor dimensions. One is called legacy, and it has two flavors: legacy and normal. Those drive the configuration of the useLegacy resource, via resValue:

        normal {
            dimension "legacy"
            applicationIdSuffix ".normal"
            resValue "bool", "useLegacy", "false"
        }

        legacy {
            dimension "legacy"
            applicationIdSuffix ".legacy"
            resValue "bool", "useLegacy", "true"
        }

The result: a legacy build opts into the legacy external storage behavior, while a normal opts into “the new normal” filtered external storage.

Default Conditions

If your targetSdkVersion is 28 or lower, you will have legacy external storage behavior by default, as if you have opted out via android:requestLegacyExternalStorage="true".

However, once you set your targetSdkVersion to 29, you will have filtered external storage by default.

It is best if you add android:requestLegacyExternalStorage yourself and declare, positively, what scoped storage behavior you want to have for when your app runs on Android 10 devices.

To check whether you have scoped storage or not, you can call isExternalStorageLegacy() on Environment:

    val msg =
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Environment.isExternalStorageLegacy())
        "This app has legacy external storage"
      else "This app has Q-normal external storage"

Note, though, that this will only return a valid value if you have a <uses-permission> element for READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE in the manifest. Otherwise, it always returns false, even if you have opted into legacy storage.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.