Random Musings on Q Beta 4

Each time Google releases a new developer preview beta, I putter around 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.

We are now up to Beta 4, so this extends upon my notes for Beta 1, Beta 2, and Beta 3.

Q Beta 4 is supposed to have the final APIs. However, I was surprised as to how much is different in Q Beta 4.

Hey, Can We Call It API Level 29 Now?

Yes, we can. We no longer have to dance around the API level. Build.VERSION_CODES.Q has the proper value instead of the 10000 placeholder. And you are able to publish apps with targetSdkVersion set to 29 if you so choose… though you may or may not be ready for that just yet.

So, What’s Up with Scoped Storage?

In a nutshell, it changed again, based on the description in the docs.

The sandboxes are gone. Instead, there is one unified external storage location that all apps and the user sees. However, apps only see their own files in external storage, in general. So, instead of scoped storage being sandboxed, it is filtered instead.

From the user’s standpoint, this should be simpler. Now files that apps write to external storage will be where they had been previously. This is really important for legacy apps that are not being regularly updated and which might never adapt to the Storage Access Framework. Yet, at the same time, apps should still unable to manipulate other apps’ files through the filesystem, meaning that users still get enhanced security.

Also, apps are still able to opt out of the filtered view, at least until next year sometime, when targetSdkVersion 29 becomes required for the Play Store and select other app distribution channels.

My biggest fear right now is that there have been so many changes over the past three months that we are at risk of more bugs than we might otherwise have had.

Note that getExternalStorageDirectory() and getExternalStoragePublicDirectory() on Environment are now deprecated, to further steer developers towards using the Storage Access Framework or MediaStore.

I will be “kicking the tires” on this stuff today and will write up more about the current state of scoped storage tomorrow.

What Else Got Deprecated or Removed?

The biggest surprise — and a pleasant one — is that android:sharedUserId is deprecated. Moreover, it is planned to be dropped entirely in some future release. For device manufacturers, android:sharedUserId was a handy shortcut for data sharing, but even there it really was not a particularly good practice. For ordinary app developers, android:sharedUserId was a footgun, one that I have been advising against people using since the very beginning. Having an app suite is awesome, but please use IPC for data sharing, not some sort of shared file that may or may not be managed properly for simultaneous multi-process access.

DownloadManager deprecated some things, as a side effect of scoped storage:

  • addCompletedDownload()
  • allowScanningByMediaScanner() on DownloadManager.Request
  • setVisibleInDownloadsUi() on DownloadManager.Request

Items listed in the new MediaStore.Downloads collection will be what appears in the Downloads UI now. So, if you have content that was not downloaded to the Downloads/ directory by DownloadManager, and you want it to appear in the Downloads UI, you need to write it to a Uri supplied by MediaStore.Downloads, I guess.

A bunch of fields from MediaStore.MediaColumns were removed, including ORIENTATION, DURATION, and DATE_TAKEN. If your app has been querying on those columns, they may no longer be available to you.

Also, MediaPlayer2 and related classes vanished without a trace.

They Didn’t Add Anything New, Did They?

Well, yes, they did, a lot more than I would expect at this late stage. Here are a few things of note:

  • Activity now has onGetDirectActions() and onPerformDirectAction(). A DirectAction is an opaque identifier of something that somebody can do with or in the activity. The idea is that an activity can supply the available actions in onGetDirectActions(), and a VoiceInteractor can let the user act upon one. The chosen action then gets delivered to onPerformDirectAction(), for the activity to go do something. Unfortunately, there isn’t a lot to go on in terms of how all of this is supposed to work. Hopefully, more documentation is forthcoming.

  • Intent now has getIdentifier() and setIdentifier(). The identifier “is an arbitrary identity of the Intent to distinguish it from other Intents”. My assumption is that this does not affect Intent routing and is basically a specific “extra” bit of data that gets passed along. It is unclear what the value of this is over, well, extras.

  • Intent also adds CATEGORY_APP_FILES to identify file managers.

  • There are four new DisplayMetrics screen densities: 140, 180, 200, 220. Those are the first new low-end densities we have had in years, and it is unclear what hardware would have such screens. It’s possible this is tied to desktop mode, if those are meant to be used for certain external displays. Regardless, most developers will not need to worry about these, as Android will scale mdpi or hdpi drawables for you. But, if you have other code that cares about these DENSITY_ constants on DisplayMetrics, you have four more to deal with.

  • android:hasFragileUserData is a new manifest setting (I’m guessing on <application>). “If true the user is prompted to keep the app’s data on uninstall”. This seems ripe for abuse, but I can see where it might be useful for some apps.

What Was Renamed?

One common thing late in the beta sequence is to have classes and methods be renamed, as somebody lost a fight in a code review or something. Of particular note:

  • isExternalStorageSandboxed() on Environment is now isExternalStorageLegacy()

  • ContentResolver.TypeInfo is now ContentResolver.MimeTypeInfo

They Must Be Done With Changes Now, Right?

Perhaps not.

There is still a reference to RecoverableSecurityException being enabled in a future beta. Certain types of I/O might throw that exception, which contains a RemoteAction that you can use to bring up some UI to help recover from that exception. In particular, if you do not have rights to modify some media, you might get a RecoverableSecurityException, where the RemoteAction would bring up UI to allow the user to grant you write access. It is unclear if this might show up in a future Q beta, whether it already is in Beta 4 (without the docs being changed), or what.

OK, So Where Do We Go From Here?

In theory, there should be few changes from here on out in terms of the API. There may yet be bug fixes, which is good, as a bunch of the bugs that I filed are still reproducible on Q Beta 4. But if you have been holding off testing your app with Android Q, waiting for things to stabilize, you should not wait any longer. I expect Android Q to ship in final form to end users in 2-4 months, and you will want to make sure that your app will survive that upgrade.

As for me, I will update Elements of Android Q shortly to reflect some of these changes and explore more dusty corners of this release, such as desktop mode.