Other Problems To Consider

We will be running into all sorts of problems as a result of scoped storage. Some we can identify now, while others will become apparent over the coming months as we start to grapple with the changes.

Here are some possible problems that you will need to consider with your app.

Advertising Support for Files in the Manifest

With scoped storage, there will be files on the external storage filesystem that another app can access that your app cannot. The other app might try using a file: Uri with some implicit Intent, such as ACTION_VIEW. While the file: scheme is banned on Android 7.0+, that is a soft ban implemented by StrictMode, and there are ways for apps to get around that. Or, the app may be rather old, pre-dating the ban.

However, it is very unlikely that your app will be able to work with such a file: Uri, as you have virtually no access to external storage that might be accessible to another app.

As such, if you have an <intent-filter> with a <data> element for android:scheme="file", you may receive Uri values that you cannot use.

Consider moving that <intent-filter> to an <activity-alias>, where you use a boolean version-dependent resource to conditionally enable that <activity-alias> on Android 9.0 and older. Then, you will not accept file: Uri values on Android 10 and newer devices. The ConditionalFile sample module in the book’s sample project demonstrates this technique.

We have a boolean resource named supportFileScheme. This is set to true in res/values/bools.xml and false in res/values-v29/bools.xml. So, supportFileScheme will be false for Android 10 and higher, true otherwise.

In our manifest, we have one <activity> element, for the typical MainActivity class. It has two <intent-filter> elements: the standard launcher, and one advertising support for ACTION_VIEW for text/plain content:

    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="https" />
        <data android:scheme="content" />
        <data android:mimeType="text/plain" />
      </intent-filter>
    </activity>

Notably, this advertises support for the https and content schemes, both of which are supported on Android 10 as well as older versions of Android. On older versions of Android, you could have file in here as well, but we want to avoid that on Android 10. Unfortunately, neither <data> nor <intent-filter> have an android:enabled option that we can use.

So, we split the file support out to an <activity-alias>:

    <activity-alias
      android:name=".FileAlias"
      android:enabled="@bool/supportFileScheme"
      android:targetActivity=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="file" />
        <data android:mimeType="text/plain" />
      </intent-filter>

    </activity-alias>

This alias points to MainActivity, so the effect is that it adds another <intent-filter> to MainActivity. That <intent-filter> is a clone of the ACTION_VIEW one from MainActivity, except that the scheme list is now just file. And, on the <activity-alias> itself, we have android:enabled="@bool/supportFileScheme", so this alias will only be enabled on Android 9.0 and older.

The activity itself just shows a Toast with the string representation of the Intent used to start the activity.

This will give us what we want: ACTION_VIEW support for file only for Android 9.0 and older, with support for https and content for all Android versions.

Since the app does not actually use the Uri values supplied to it, you can test this behavior using simple adb commands. This one will pop up the Toast on all Android versions, as it uses a Uri with a content scheme:

adb shell am start -t text/plain -d content://respect.mah.authoritah/whatever

This one, though, will not match on Android 10, since it uses the file scheme:

adb shell am start -t text/plain -d file:///storage/emulated/0/whatever.txt

Assuming Valid Uri Values from ACTION_SEND

Unfortunately, the way that ACTION_SEND works does not allow you to filter incoming requests by scheme. As a result, you may get EXTRA_STREAM values with a file: Uri that you cannot access.

Ordinarily, you might just allow those errors to bring up some generic “oops” dialog, snackbar, etc. In this case, you should consider adding a more specific error message, indicating that the app that was used to send content to you is old and needs to be updated.

Assuming Valid Uri Values from the Clipboard

Similarly, you can get a Uri from the clipboard or via drag-and-drop, where you cannot filter by scheme. Add custom error messages here as well, indicating that the source of the Uri is old and needs to be updated.

Assuming Content is Seekable

Methods like mark() and reset() on InputStream may or may not work on a stream obtained for content identified by a Uri. Those methods usually work if the stream is backed directly by a file on the filesystem. They usually will not work for a stream that requires the ContentProvider to process the data, such as decrypting an encrypted file.

As a result, a Uri from things like the Storage Access Framework may or may not work with code that relies on the ability to rewind the stream, such as some media libraries.

If you in your own code rely on mark() and reset(), try to switch to some sort of buffering strategy, so your “rewind” operations work on data that you already read and do not assume that you can rewind the stream itself.


Prev Table of Contents Next

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