The CommonsBlog


The Death of External Storage: More of the Story

A month ago, I wrote a blog post about the changes in external storage on Android Q.

I intentionally skipped one finding, because it really seemed like a bug. However, apparently, it is working as intended.

So, let’s revisit what you can do with the external storage filesystem in Android Q.

And, as a reminder: unless Google changes things, these rules affect all apps, regardless of targetSdkVersion (except for apps already installed on a device that gets the Q upgrade).

Where Can You Write?

You cannot write to the standard directories available from Environment.getExternalStoragePublicDirectory(). So, you cannot write to DIRECTORY_DOWNLOADS or DIRECTORY_MUSIC or places like that. Largely, that is because they already exist and were not created by your app.

However, you can:

  • Create files in the root of external storage (Environment.getExternalStorageDirectory())

  • Create directories in the root of external storage

  • Create files in those directories that you created

Basically, if it already exists — such as directories that were created as part of setting up the device — you have no write access. If you create it yourself, though, you do have write access.

What Can You Read?

If you wrote it, you can read it.

However, that is it. You cannot read the contents of files or directories that were created by other means, using filesystem APIs.

What Permissions Do You Need?

None.

No, Seriously, What Permissions Do You Need?

None. This was the part that I thought was a security bug. ¯\_(ツ)_/¯

If you have targetSdkVersion 'Q', or if you have targetSdkVersion 28, you can write to the root of external storage and read back what you wrote, without any permissions. Presumably, this holds true for at least some older targetSdkVersion values, but I only tested these two.

The reason why READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are deprecated is that all of external storage — except for existing files and directories — works like getExternalFilesDir():

  • You do not need any permissions
  • What you create is removed when the app is uninstalled or if the user clears storage for the app

What Can Other Apps Do With Your Files?

What you create and write in external storage via the filesystem is inaccessible to other apps via the filesystem, no matter where it is.

What you create in getExternalFilesDir() will be accessible via the Storage Access Framework, but what you create in the root of external storage is not. As a result, anything you put in the root of external storage (or custom directories off of the root) is private to your app, except to the extent that you expose them via FileProvider or something.

What About the User and a USB Cable?

Similarly, what happens here depends on which portion of the external storage filesystem that you use.

For getExternalFilesDir() and kin:

  • Files and directories that you create are visible to the user

  • Files and directories that the user creates are visible to you

For the root of external storage:

  • Files and directories that you create are not visible to the user (MediaScannerConnection seems to not work for those files, and ACTION_MEDIA_SCANNER_SCAN_FILE has been deprecated with no stated replacement)

  • Files and directories that the user creates are not visible to you

Even as a developer, adb does not show app-created files in the external storage root directory. Nor does Android Studio’s Device File Explorer.

So, What Should I Do Now?

In general, my previous recommendations still hold up:

  • Use the methods on Context like getExternalFilesDir() to find places to write, if you need to make the files available to the user via USB

  • Use the Storage Access Framework

  • Use the MediaStore for media

  • Use FileProvider to make your content available to other apps for outbound requests (e.g., ACTION_VIEW and ACTION_SEND Intents)

  • If applicable, support ACTION_VIEW and/or ACTION_SEND in your manifest, so other apps can use those Intent actions to send you content

If you have existing code that writes files to the root of external storage or some custom directory off of it, that code will continue to run. However, the user will be unable to access those files, except through your app. In general, it will be better to migrate those to getExternalFilesDir(), if user access to those files is important.


It is entirely possible that there are yet other combinations, scenarios, and mysteries yet to be discovered. The key is that you should test your app early and often on Android Q.

Apr 22, 2019


"Elements of Android Q" Version 0.2 Released

Subscribers now have access to Version 0.2 of Elements of Android Q, in PDF, EPUB, and MOBI/Kindle formats. Just log into your Warescription page to download it, or set up an account and subscribe!

There are two new chapters, on bubbles and share targets.

A few new items were added to the chapter on miscellaneous changes.

And everything was brought up to date for Q Beta 2.

The next update to this book should be after Google I O and the release of Q Beta 3.

Apr 15, 2019


About Share Targets and Magic Names

If you are trying to use androidx.sharetarget:1.0.0-alpha01 and are wondering why it’s not working correctly, you may not have used the right magic name.

androidx.sharetarget is a new AndroidX library that offers support for Android Q-style share targets on older devices. It supplies its own ChooserTargetService implementation (androidx.sharetarget.ChooserTargetServiceCompat) that you point to, and that in turn implements the Android 6.0-9.0 Direct Share API. It builds the ChooserTarget objects based on your Android Q share targets.

Android Q’s share targets are based on a combination of dynamic shortcuts using ShortcutManager and a new <share-target> element in a static shortcuts XML resource. Static shortcuts are identified in the manifest by adding a android.app.shortcuts <meta-data> element to your launcher activity, pointing to an XML resource.

Normally, you can name that XML resource whatever you want, so long as it is a valid resource name.

For androidx.sharetarget, at least for 1.0.0-alpha01, you must name that resource shortcuts.xml.

That is because 1.0.0-alpha01 hard-codes that name, with a TODO comment to fix it:

    private static XmlResourceParser getXmlResourceParser(Context context) {
        // TODO: Parse the main manifest to find the right Xml resource for share targets
        Resources res = context.getResources();
        return res.getXml(res.getIdentifier("shortcuts", "xml", context.getPackageName()));
    }

So, with luck, this restriction will be lifted in a future update to the androidx.sharetarget library.

In general, try to avoid magic names (such as shortcuts.xml). And where you need a magic name, make sure you document it.

Apr 14, 2019


"Exploring Android" Version 0.5 Released

Subscribers now have access to an update to Exploring Android, known as Version 0.5, in PDF, EPUB, and MOBI/Kindle formats, in addition to the online reader. Just log into your Warescription page and download away, or set up an account and subscribe!

This update adds seven more tutorials, covering things like RecyclerView and the Navigation component. It also updates the tutorials for Android Studio 3.3.2, though outside of the Android Gradle Plugin version, there was little to change.

I hope to get another update for this book out before Google I O.

Apr 08, 2019


Random Musings on Q Beta 2

Each time Google releases a new developer preview beta, I wander through the API differences report the high-level overviews, and even the release blog post, to see if there are things that developers should pay more attention to. I try to emphasize mainstream features that any developer might reasonably use, along with things that may not get quite as much attention, because they are buried in the JavaDocs.

And if it feels like you just read one of these, it was about three weeks ago. Time flies.

What Got Bigger and Smaller at the Same Time?

Q Beta 1 introduced a “roles” system, whereby one app per role type could hold a role and be granted superpowers. The documentation for roles was a mess, but roles seemed to work.

In Q Beta 2, things got weird(er).

On the one hand, the RoleManager JavaDocs show new roles: ROLE_ASSISTANT (presumably for the Assist API), ROLE_CALL_REDIRECTION (presumably for forwarding all your robocalls to your arch-nemesis), and ROLE_CALL_SCREENING (presumably for identifying the robocalls to forward to your arch-nemesis).

On the other hand, in the days leading up to Q Beta 2, an eagle-eyed subscriber pointed out to me that the Q documentation page for roles vanished. Even stranger, the scoped storage documentation no longer mentions roles, and you used to need to hold certain roles to have read-write access to certain content collections.

So, I have no idea what’s going on here. Q Beta 3 is due out around the time of Google I|O, so perhaps we will get some clarity then.

Speaking of Scoped Storage, WTF?

The biggest change with scoped storage in Q Beta 2 is that it is enabled by default. Once you flash your device or get your emulator up on Q Beta 2, you should see that adb shell getprop sys.isolated_storage_snapshot results in true. Now, more developers will start to encounter the stuff that I blogged about recently.

However, roles are no longer required. There are now three “strongly-typed” permissions for read access to external storage:

  • READ_MEDIA_AUDIO
  • READ_MEDIA_IMAGES
  • READ_MEDIA_VIDEO

These are dangerous permissions, so you will handle them the same as READ_EXTERNAL_STORAGE. If your app has targetSdkVersion set to Q, you can request those three permissions, and you get the same level of access that an app targeting API Level 28 or lower gets from requesting READ_EXTERNAL_STORAGE on a Q device. That is still very limited access, only through the MediaStore, but it is better than nothing.

The docs now have a section on using content with the NDK, advising you to use a FileDescriptor. What the docs do not tell you is that you can only get a FileDescriptor if the content is backed by a file or something else that can be mapped to a FileDescriptor. Not all content fits that description, particularly for "rw" modes. Be prepared to gracefully degrade if openFileDescriptor() returns null or throws a FileNotFoundException.

The recommended way for you to create audio, image, or video content that survives an app uninstall is to use MediaStore. This gets a bit more complicated due to some deprecations introduced in Q Beta 2:

  • getBitmap() is deprecated. Google steers you to ImageDecoder, though BitmapFactory would also be an option if you wanted code that works on older devices.

  • query() is deprecated. Google recommends using query() on ContentResolver, which hopefully is what you were using already anyway.

  • insertImage() is deprecated, which surprises me. Google steers you to a new “pending” capability in MediaStore, which is almost completely undocumented.

And the security bug that I filed is still present. The bug report itself has been ignored, outside of a couple of bots.

What Else Is Big?

Q finally got a user-visible feature of note: bubbles.

As a developer, I am grateful that this sort of floating interaction is being standardized. Having be an add-on for notifications should simplify development. And the fact that the actual content comes in the form of a tiny resizeable Activity means developers get another nudge towards being able to support split-screen and freeform multi-window modes.

As a user, I am grateful that I can turn this thing off.

What Else Do Manufacturers Hope Is Big?

The foldables march continues. The top-line announcement is that the emulator is supposed to now have the ability to create foldable emulator images.

“Foldable” is now also a formal system feature, in the form of PackageManager.FEATURE_FOLDABLE and android.hardware.type.foldable. So, if you create an app that requires a foldable (???), you can register in the manifest to say that you need that system feature.

What Else Changed from Q Beta 1?

BiometricPrompt.Builder used to have a setEnableFallback() method, which has been renamed to setAllowDeviceCredential(). That name is more reflective of its role: to allow the user to use a PIN or password as an alternative to biometrics. BiometricPrompt also now has a BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL error code, which appears to be returned if you called setAllowDeviceCredential(true) and the user has neither biometrics nor a PIN/password set up.

What Else Seems Intriguing?

ContentResolver now has a getTypeInfo() method. Given a MIME type, you get back a TypeInfo object that contains an icon, label, and description of the MIME type. This seems really handy for lists of content. However, the API returns all non-null values, so it is unclear what happens if you hand getTypeInfo() an invalid MIME type, such as rutabaga/marshmallow.

ContentResolver also has a wrap() method. You pass wrap() an instance of a ContentProvider, and wrap() returns a ContentResolver that only talks to that provider. I can see this being useful in testing ContentProvider implementations.

There is a new subtype of NotificationListenerService, called NotificationAssistantService. It “helps the user manage notifications”, whatever that means. It appears that a NotificationAssistantService has more power to modify the behavior of a Notification than does an ordinary NotificationListenerService.

There are a handful of new methods, such as getAttributeResolutionStack() on View, that appear to be here to help you determine how your various styles and themes are affecting particular widget and attributes of those widgets.

There is a new LocusId class, described as an “identifier for an unique state in the application”. What is curious is that it is supposed to be a very durable identifier of that state, not only surviving a reboot but surviving a backup/restore operation. That pretty much requires it to be a server-side identifier, as nothing local to the device might survive a restore operation. It is tied to an ACTION_VIEW_LOCUS Intent. However, it is unclear in what scenarios this will be used.

What Else You Got?

I will be releasing an update to Elements of Android Q in a couple of weeks. I will update what I have to reflect the changes wrought in Q Beta 2, and I will add some more chapters, probably on bubbles and the new share targets implementation.

Apr 04, 2019


Older Posts