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()
onDownloadManager.Request
setVisibleInDownloadsUi()
onDownloadManager.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 hasonGetDirectActions()
andonPerformDirectAction()
. ADirectAction
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 inonGetDirectActions()
, and aVoiceInteractor
can let the user act upon one. The chosen action then gets delivered toonPerformDirectAction()
, 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 hasgetIdentifier()
andsetIdentifier()
. The identifier “is an arbitrary identity of theIntent
to distinguish it from otherIntents
”. My assumption is that this does not affectIntent
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 addsCATEGORY_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 scalemdpi
orhdpi
drawables for you. But, if you have other code that cares about theseDENSITY_
constants onDisplayMetrics
, you have four more to deal with. -
android:hasFragileUserData
is a new manifest setting (I’m guessing on<application>
). “Iftrue
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()
onEnvironment
is nowisExternalStorageLegacy()
-
ContentResolver.TypeInfo
is nowContentResolver.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.