Mark L. Murphy's Blog, page 17
March 27, 2020
A Peek at SurfaceControlViewHost in Android R
One of the items that I found interesting in
the second half of my R DP2 random musings
was SurfaceControlViewHost. I experimented with it this week, and it at least
partially works. In a nutshell: one app can embed and display a live UI from another app.
Wait, Wut?
For some developers, this sort of cross-app UI embedding has been ���the Holy Grail���
for years. You can do a limited version of this with RemoteViews, but the
widget set is minimal by modern standards. You could create your own
RemoteViews-like structure, but keeping all of the participating apps in sync
can get troublesome. Android 9���s slices��� well, OK, those never really caught
on.
But, with Android R and SurfaceControlViewHost, it is not that hard to set
up cross-process UI delivery. There are no obvious limits as to what that UI
can look like, because the UI itself is not really shared. Instead, the two
processes seem to be sharing a Surface, with the UI-supplying process rendering
a view hierarchy to that Surface and the UI-hosting process displaying that
Surface as part of a SurfaceView.
How Do You Make It Work?
I���ll have code available on Monday, as part of the Elements of Android R release.
But, here are the basic mechanics:
Have two apps, with some sort of IPC channel between them. I elected to use
a bound service, playing with Google���s Messenger pattern for getting data between the apps.
In the source code, you will see an EmbedServer and an EmbedClient module
that represent these two apps.
Have the UI client (EmbedClient) set up a SurfaceView and identify the Display
on which that SurfaceView will appear. Then, it needs to send to the other app the dimensions
of the SurfaceView, the ID of the Display to use, and a ���host token��� obtained
from the SurfaceView via getHostToken(). All of those can be stuffed into
a Bundle for easy delivery via common IPC patterns (e.g., as part of a Message).
Have the UI provider (EmbedServer) set up that UI, such as via view binding.
When it receives the details from the client, it can set up a SurfaceControlViewHost
tied to the Display and ���host token���. It can then attach the root view of the
view hierarchy to the SurfaceControlViewHost via addView(). Then, it needs to
obtain a SurfacePackage from that SurfaceControlViewHost (via getSurfacePackage())
and send that back to the client. SurfacePackage is Parcelable, so you can send
it via any common IPC mechanism (e.g., as part of a return Message).
Once the client receives the SurfacePackage, attach it to the SurfaceView via
setChildSurfacePackage().
And that���s it. At this point, the client should be showing the provided UI in
the SurfaceView. If the provider updates that UI, the client should show the updates
in real time.
What About Input?
The docs indicate that touch events on the SurfaceView
should get sent from the client process to the provider process, with the implication
that this will trigger events on the widgets in the provider���s view hierarchy.
Unfortunately, I could not get that part to work.
That���s quite possibly a bug in my experimental code. There is very little documentation
on this, and I may have missed a step somewhere. Otherwise, it���s possible that there
is a bug in DP2.
What���s Google Going to Do With This?
I have no idea.
Seriously, they could use this for:
A richer replacement for app widgets and slices
A richer option for custom views in notifications
Embedding any of their apps in any other one of their apps (e.g., more powerful
options for launching a Hangout from Calendar)
But, my guess is that whatever they have in mind will be something I won���t expect.
What Can We Do With This?
Well, not much, insofar as this is only available on Android R. Since this requires
new methods on SurfaceView, my guess is that this cannot be backported via a Jetpack
library. For the time being, approximately 0.0% of your user base is running Android R.
However, longer-term, this opens up some interesting possibilities.
From a security standpoint, this technique should allow for us to better sandbox
untrusted content. We have had options for doing that, with dedicated low-permission
processes, but they had only classic IPC ways of getting information out of the
sandbox. Now, they can present a full UI, yet still not have any means of attacking
the client displaying that UI.
Apps with a rich third-party ecosystem of plugins could adopt this for incrementally tighter
integration with those plugins. Right now, the only easy thing is for the app to
start an activity in the plugin, if the plugin needs to supply UI. Otherwise, you
were stuck with the UI integration options I mentioned earlier, like RemoteViews.
Now, though, a plugin can provide finer-grained UI elements that could be embedded
in the core app���s UI, to offer a more seamless experience to the user.
Assuming that there is no significant performance overhead for delivering a UI
this way, this opens the doors for popular content publishers to get their
content embedded in other apps, yet still maintain complete control over that
content.
But, once again, my guess is that the best use of this tech is something that I am not currently
thinking of.
Ordinarily, I would have expected presentations on this at Google I|O. Now, in our
I|O-free world, I do not know when or how Google might provide more information
on this API and how they (and we) might use it. But, it���s something that I will
be keeping an eye on, as it���s one of the more intriguing new additions in Android R.
March 22, 2020
R Raw Paths and All Files Access
In R DP1, I could not get the ���raw paths��� feature or the ���all files access��� feature
to work.
In R DP2, both seem to be working fine.
The documentation is a bit confusing, but here is what I have seen in light
experimentation.
Raw Paths
In a nutshell, READ_EXTERNAL_STORAGE works like it did from Android 4.4 through
Android 9. If you request it, and the user grants it, you can traverse external
storage more or less as you were used to.
However, there are caveats:
The documentation mentions reduced performance. I have not attempted to do any
sort of apples-to-apples comparison, but I don���t get the sense that the performance
degradation is severe.
As the documentation notes, you still do not have access to Android/ and its
subdirectories.
I have not tried removable storage.
While the documentation emphasizes native libraries, read access works fine from Java/Kotlin.
I have not tried native library access, but I assume that it works.
Also, methods like getExternalStorageDirectory() and getExternalStoragePublicDirectory()
on Environment are still deprecated. My guess is that we are supposed to use
getDirectory() on StorageVolume, as that was added in Android R. I have
not experimented with that yet. getExternalStorageDirectory() works, at least
on R DP2, returning the conventional location.
All Files Access
Simply put, MANAGE_EXTERNAL_STORAGE is the new WRITE_EXTERNAL_STORAGE.
If you request MANAGE_EXTERNAL_STORAGE in the manifest,
and you use Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
to bring up the Settings screen for this, and the user grants the permission, then
you can write content to external storage. The effect is akin to what we had
from Android 4.4 through Android 9.
Again, though, there are caveats:
The same concerns exist around how we get the filesystem path to use.
Presumably, write performance is also reduced.
Even with this permission, you still do not have access to Android/ and its
subdirectories.
I still have not tried removable storage (hey, I���ve been busy writing this blog post).
There is no obvious means to find out if we hold this permission. It is not a
dangerous permission, so checkSelfPermission() does not work.
The issue has been marked as ���fixed���, but
I did not notice a method for this in DP2, so perhaps the method will show up
in a later developer preview.
Now What?
Given last year���s continuous changes in this area in the Q developer previews, I am
not making any final recommendations until R ships as Android 11.
Also, the fact that filesystem-style access is back in R does not change the fact that
we should be losing that access in Q. android:requestLegacyExternalStorage
is supposed to be ignored by Android 10 once our targetSdkVersion hits 29.
Assuming that remains the case (I have not re-tested it), then we still lack
filesystem-style access in Android 10, even though we have it in 9 and 11.
If so, you are still going to be needing to try to adopt the Storage
Access Framework and/or the MediaStore to handle Android 10. Plus, the
MANAGE_EXTERNAL_STORAGE permission screen is decidedly more scary-looking
than the normal dangerous permission dialog, so fewer users will be willing
to grant it.
So, I���ll be watching this area in DP3 and beyond, to see what changes, if anything.
However, it looks like external storage is making a comeback, after I left it for
dead almost exactly one year ago.
March 20, 2020
More Random Musings on R DP2!
Rich Hong pointed me to
the DP1 -> DP2 API differences report.
Which is really good, because, as it turns out, all the cool stuff isn���t in the
actual R DP2 docs.
So, here are some more random musings, based on that API differences report, and
building upon yesterday���s original set of R DP2 musings.
Things That Show How Android Is In Control
As XDA-Developers mentioned a few hours ago,
there is a new API for adding ���quick controls��� to the menu we get on a long-press of the POWER
button. Apps can implement a ControlsProviderService
to advertise options for this ���quick controls��� area. This appears to work a bit
like TileService does for adding tiles to the notification shade. The
available types of controls are defined as constants on a DeviceTypes
class, and the candidates are mostly appliances and similar sorts of household
items��� and, apparently, a pergola.
However, I feel quite confident that enterprising developers will find ways
to use this for ���device types��� that are somewhat broader in nature.
Things That Share
In Android 10, we got SurfaceControl.
I blew past it, in part because I couldn���t figure out what it was for our how we
would really use it.
In R DP2, we now have SurfaceControlViewHost.
Its JavaDocs have a very interesting sentence:
The primary usage of this class is to embed a View hierarchy from one process in to another.
The implication is that we might now have something that developers have been
asking for since the very early days of Android: the ability to embed the UI
of one app in another. Whether that is the intended purpose or not remains to be
seen, as all of this is still rather under-documented.
Things That Affect Security
There is a new VpnManager
API for developers of VPN clients.
We also now have VerifiedKeyEvent and
VerifiedMotionEvent. We seem to
get these from verifyInputEvent() on InputManager,
whose JavaDocs state:
Verify the details of an InputEvent that came from the system. If the event did not come from the system, or its details could not be verified, then this will return null.
It is unclear what the scenarios are where the InputEvent would not come from ���the system���,
in part because there is no real definition here of ���the system���. However, if you
have a highly-secure input scenario (e.g., passcode for a banking app), this
may be worth investigating further.
Things That Are Getting Massaged
If you have been using various ways of implementing full-screen UIs, such
as setSystemUiVisibility(), your current API calls may now be deprecated.
That is moving to WindowInsetsController
and WindowMetrics.
The ChooserTarget and ChooserTargetService that were the original approach
for direct-share targets are now deprecated in favor of Android 10���s
Sharing Shortcuts API.
What Happens Next?
The
collapse of Google I/O does start to raise
questions about Android R���s rollout. It would not surprise me if things get
delayed, both at the level of the developer previews and betas and at the level
of a final release. After all, Google has to do what everyone else is doing: try to get stuff
done in the face of a pandemic.
However, eventually, the sun will shine again, SARS-CoV-2 will have reduced impact,
and Android R will ship. Between now and then, I will write and blog what I can
on what Android R means for us developers.
March 19, 2020
Random Musings on the R Developer Preview 2
Each time Google releases a new developer preview, I read what I can
to see if there are things that warrant more attention from
developers. 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.
This time, I can���t readily do the ���buried in the JavaDocs��� part, because
the release notes
lacks a link to the DP1 -> DP2 API difference report. Instead, it has two
links pointing to the API 29 -> DP2 report,
which makes it more difficult for me to identify what is new to DP2.
If this gets fixed, I may
follow up with another blog post.
In the meantime, here are some musings, as random as ever���
Things That Might Piss Off Users
Apparently, Android R will nag users to not use a USB headset
with apps that��� don���t record audio. The workaround: have the app say that it records audio.
��\_(���)_/��.
Things That Could Break Your App
I worry that the package visibility changes
are going to break more apps than perhaps Google realizes. While I haven���t tested
this aspect of R DP2 yet, it appears that your app now can���t find out what other apps are installed,
on a general basis. The cited example is queryIntentActivities(), but to make
this really work you would need to seriously lobotomize PackageManager.
You can whitelist certain packages and certain <intent-filter>
structures to try to get by this for certain use cases. And, this is where
the mysterious QUERY_ALL_PACKAGES permission seen in DP1 comes into play ���
this permission removes these new restrictions. Given the ���look for Google Play
to provide guidelines for apps that need this permission��� caveat, it is safest to assume
that if you try using it, eventually you will be banned from the Play Store by a
bot.
If your app has been dutifully following the new Android 10 rules for
background location access��� get ready for new user flows.
If your app has been clearing the cache of other apps��� sorry, but
that appears to no longer be an option.
Probably the move to Conscrypt for SSLSocket
will not cause problems for most apps, but it���s something to keep an eye on.
Things That Are Generally Positive
android:preserveLegacyExternalStorage
might be useful for some developers, though I suspect not all that many.
Basically, it allows you to say that users who upgrade your app get old-style
external storage while new users get scoped storage, by my read of it.
Being able to use more than one camera
seems nice.
The blog post
mentions some new window inset options for more synchronized behavior with
soft keyboards as they collapse and expand. Strangely, I don���t see this in the main Android R
release documentation. It���s a slick feature, albeit one that I do not expect will get
adopted that much. As with Android 10���s gesture-based navigation, I think Google
is seriously over-estimating developers��� willingness to rewrite UIs for these sorts of things.
After all, we can���t get apps to support landscape, and that feature has been around since API Level 1.
So, while I���m sure highly-polished apps will use this new synchronized IME
stuff to become even more highly-polished��� most apps will muddle along as they have been.
Things That Make Me Go ���Hmmmmmm������
The per-process network access control
feature seems great��� except that the supplied XML is mystifying. Where
does this go? What does an empty <process /> element mean? What does
a <process> element without children mean? Does the allow-permission and
deny-permission element names, coupled with the fully-qualified permission names,
mean that this works for all permissions, not just android.permission.INTERNET?
What is referred to as dynamic intent filters
seems to overstate the case: the only dynamic element is the roster of supported
MIME types. The rationale given for this feature is:
This is a problem for virtualization apps (such as virtual machines and remote desktops) because they have no way of knowing exactly what software the user will install inside them.
That rationale seems odd to me. I certainly can���t rule it out, but it is unclear
under what circumstances ���virtual machines and remote desktops��� would know about
particular MIME types that they should support dynamically. Plus, virtual machines
would seem to be in violation of Play Store policies regarding dynamic code loading
just by existing. The feature seems fine, but something seems fishy.
What Happens Next?
I think I have found enough stuff that works to warrant releasing an
Elements of Android R in the coming weeks, so stay tuned!
March 5, 2020
Android R: A Rough Start
I had hoped to have a first version of Elements of Android R out soon.
However, so far, the only Android R feature that I have found that works
as advertised is the one-time permission grant. Everything else that I have tried ���
from ���All Files Access��� to
raw paths to
bubbles ��� has failed.
And I���m spending time fighting regressions to boot (though, technically, I
suppose the bubbles problem is a regression���).
I will continue poking at Android R. However, on the
whole, I am waiting for DP2, and I will hopefully be able to write more positive
things about Android R by then.
March 3, 2020
Scoped Storage Stories: More on RecoverableSecurityException
Android 10 and higher are greatly restricting access to external storage
via filesystem APIs. Instead, we need to use other APIs to work with content.
This is the 13th post in a seemingly never-ending series, where we will explore how to work with those
alternatives.
I wrote about RecoverableSecurityException in an earlier post on
updating the content from other apps.
I thought I was done with it.
Silly me.
Specifically, this Stack Overflow question
dragged me back into it, as Stack Overflow user joakimk was not getting that
exception when trying to update the DESCRIPTION column for an image:
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DESCRIPTION, "Some text");
int res = getContext().getContentResolver().update(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values,
MediaStore.Images.Media._ID + "= ?", new String[]{sImageId});
Here, the image ID is pointing to an image that was not created by joakimk���s app.
In theory, on Android 10+, this should trigger a RecoverableSecurityException,
so we can ask the user for permission to work with that piece of content. In this
case, the update() call simply returned 0, meaning no rows were affected.
This simple snippet of code highlights a few problems and other considerations with how
RecoverableSecurityException works.
The Uri is the Key
The biggest problem is that RecoverableSecurityException is on a per-Uri
basis. The exception contains an IntentSender that can be used to get permission
to modify the content identified by that Uri.
A side effect of that limitation is that the update() call must use the Uri
identifying the content, not a collection Uri plus a quasi-WHERE clause to identify
it. After all, that WHERE clause might identify more than one piece of content.
So, you wind up with code more like this:
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DESCRIPTION, "Some text");
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageId);
int res = getContext().getContentResolver().update(uri, values, null, null);
Here, we use ContentUris.withAppendedId() to create a Uri pointing to the
specific image, and we pass that Uri to update(). This update() call
will now throw a RecoverableSecurityException.
The Permission is Not Enough
Unfortunately, that RecoverableSecurityException is not going to do you a lot
of good in this case��� because DESCRIPTION cannot be modified, even if the user
grants your app permission to modify the content. For some columns, MediaStore
just refuses to apply the update.
TAGS, however, can be modified. So, for example, this sample Kotlin code
works:
suspend fun setTags(id: Long, tags: String) =
withContext(Dispatchers.IO) {
val values = ContentValues().apply {
put(MediaStore.Video.Media.TAGS, tags)
}
val uri = ContentUris.withAppendedId(collection, id)
context.contentResolver.update(uri, values, null, null)
}
(taken from this sample project)
The Permission Grant is Durable
In my earlier post on RecoverableSecurityException, I did not explore how long
the permission grant lasts. If I had to guess, I would have guessed that it lived
just for the lifetime of your process.
Instead, it appears that the permission grant is more durable than that. While
I cannot say that it lasts ���forever���, it certainly lasts for more than just one
process.
Android R is Unhappy
While Android 10 handles RecoverableSecurityException just fine, Android R DP1
does not, crashing when we
display the system dialog to allow the user to grant permission to modify the
content. That crash comes from the MediaProvider, not the app, so it appears to be
a bug that hopefully will get fixed.
Many thanks to joakimk for the assistance in identifying the problem and its
solution!
The entire series of ���Scoped Storage Stories��� posts includes posts on:
The basics of using the Storage Access Framework
Getting durable access to the selected content
Working with DocumentFile for individual documents
Working with document trees
Working with DocumentsContract
Problems with the SAF API
A specific problem with listFiles() on DocumentFile
Storing content using MediaStore
Reading content from the MediaStore
Modifying MediaStore content from other apps
Limitations of MediaStore.Downloads
The undocumented Documents option
More on RecoverableSecurityException
February 24, 2020
���Elements of Android Jetpack��� Version 0.8 Released
Subscribers now have
access to Version 0.8 of Elements of Android Jetpack,
in PDF, EPUB, and MOBI/Kindle formats. Just log into
your Warescription page to download it,
or set up an account and subscribe!
This update adds three new chapters:
Coping with New Android Versions
Deciding Where to Go From There
It also updates a bunch of the dependencies and tweaks the material to match, such
as replacing the deprecated ViewModelProviders.of() with new ViewModelProvider()
or by viewModels(). Similarly, the material on saved instance states in
the chapter on processes now uses the SavedStateHandle approach
with ViewModel.
And, as usual, it fixes lots of bugs.
February 21, 2020
Random Musings on the R Developer Preview 1
Each time Google releases a new developer preview, I rummage through
the API differences report
the high-level overviews,
and even the release blog post,
to see if there are things that warrant more attention from
developers. 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.
As with the previous three releases, R seems to be lacking a prominent
user-facing feature. I have no idea what I will
tell my mother that she���ll gain if her phone gets the R update at some point.
Even the changes for developers seem relatively modest this time around.
It���s almost like Android is maturing as a platform!
That being said, here are some things that caught my eye.
What Will Piss Off Users
The phrasing around the file and directory access restrictions
puts the blame on developers. How dare we ask for the Downloads/ document tree, or
for files in Android/data/ directories!
In reality, developers don���t do that. Developers simply fire off ACTION_OPEN_DOCUMENT,
ACTION_OPEN_DOCUMENT_TREE, or ACTION_CREATE_DOCUMENT Intents. Where the user
winds up choosing is up to the user.
So, in reality, the restrictions listed in this section will be placed on users,
not developers. And I have absolutely no idea what sort of messaging Google could
use in the Storage Access Framework UI that would justify to the user blocking them
from navigating to certain portions of external and removable storage. ���Sorry, you
can���t choose Downloads/, because ��\_(���)_/����� doesn���t strike me as something that will
make users very happy.
I am all for improved user control over privacy and security concerns, but the
boundaries between what is and is not allowed need to make sense to users and,
secondarily, to developers.
What Will Piss Off Developers
On the one hand, the ���All Files Access���
support should be a good thing. It gives file managers and similar sorts of apps
an option for accessing more stuff on external and removable storage.
However, it is not really ���all files���, as Android/data/ is still blocked off.
IMHO, the next step is to use the one-time permission system to allow these
apps to have access to these protected areas (e.g., so a backup app can actually do
a backup).
The background location restrictions
are confusing as documented. I���ll be playing around with these and hopefully will work
out some samples soon.
What Got Better
The batch media file access and access to media via ���raw file paths���
represent welcome improvements to the scoped storage restrictions added in Android 10.
The data access auditing ���
allowing you to track where you are accessing sensitive data and by what app
feature ��� is promising, albeit confusing. I���m sensing a book chapter in my future���
The shared datasets feature,
for apps sharing large quantities of static data ��� is a nice addition, but it is
one of those things that may be difficult to backport. And, without a backport,
its utility will be limited until Android 11+ is dominant, which will take years.
Resource loaders
is a hugely useful addition, so we can create an officially-supported solution for
updating resources and assets without shipping a new app update. I can see appcompat
gaining a backport of this.
Conversations are interesting
if somewhat narrow in effective scope.
What���s Old and New Again
In summary:
Bubbles: back!
Blossom and Buttercup: still missing
What���s Obscure But Cool
We can get a list of ���historical process exit reasons���,
including crashes, low-memory pressure, permission changes, etc. IMHO,
there should be a unique identifier on these, but
even without it, having this data can be very useful for understanding what���s going
on with the app in production.
There are a new series of methods on MediaStore for creating PendingIntent
objects that perform actions against the store itself, such as
marking items as favorites
or deleting items.
These could be attached to notification actions, app widget buttons, and other places
where we need a PendingIntent, and we don���t have to fuss with our own code
for implementing them.
What Has Me Worried
The documentation around one-time permissions
is too limited. In particular, the explanation of the lifetime of the permission
grant is unclear. Some developers claim that you need to re-request the permission
even after onPause() of some activity, whereas in light testing I���m finding
that the permission grant lasts for the entire process lifetime.
There is a new QUERY_ALL_PACKAGES
permission, which ���allows query of any normal app on the device, regardless of manifest declarations���
(emphasis added). I worry about what it means for an app to declare itself as not being
visible via PackageManager.
What���s Dead
Google is continuing the trend of deprecating highly-visible classes that have
better replacements nowadays. In Android 11, this includes:
AsyncTask: use coroutines, RxJava, custom executors, etc.
IntentService: use JobIntentService or a regular Service
ListActivity and ExpandableListActivity: use a RecyclerView
TabHost and TabWidget: there are a million tab solutions out there, pick one
Also, they deprecated TimingLogger and LauncherActivity. I rarely used
TimingLogger and might never have known about LauncherActivity.
setView() on Toast is dead.
If you are using this, it will stop working once your targetSdkVersion reaches R.
And, we can no longer get official translations
of ���yes��� and ���no���, inexplicably.
What Makes Me Go ���Hmmmmm���
As usual, there is a bunch of stuff that shows up in the API differences report
that is interesting, confusing, and sometimes both:
ACTION_WIFI_ADD_NETWORKS
seems like an interesting addition to the WiFi network suggestion APIs from
Android 10, but I���d need to put it to use to have a better sense for what
the flow will be like.
Usually, we try to avoid ANRs (���application not responding��� dialogs). In
Android 11, we can ANR ourselves.
ActivityManager has an undocumented isLowMemoryKillReportSupported() method.
A tiny bit of protobuf
wandered into the SDK, which seems odd. Why would this be a framework class?
The inline presentation API
seems intriguing but lacks any obvious things that use it.
There is an APP_SEARCH_SERVICE system service name
that supposedly is for an AppSearchManager, but the SDK does not have an entry
for AppSearchManager. Perhaps this will show up in DP2.
FileIntegrityManager
has a sweet name, though its use cases seem fairly limited.
Devices can have a feature (e.g., uses-feature) for being ���a Context Hub���.
It is unclear what a ���Context Hub��� is and why it needs a Title Case name.
There are two new foreground service types: camera and microphone
It is very strange that they added getStorageDirectory(),
since most apps cannot access it. Perhaps it is there for MANAGE_EXTERNAL_STORAGE
holders.
We got java.util.concurrent.Flow.
If you are using coroutines, make sure you don���t import the wrong Flow.
What Happens Now?
I am working on Elements of Android R and should have a 0.1 version out for
subscribers within a few weeks. And, I will be continuing to blog any scary findings
(scary bad or scary cool) as I stumble upon them.
February 15, 2020
Scoped Storage Stories: The Undocumented Documents
Android 10 is greatly restricting access to external storage
via filesystem APIs. Instead, we need to use other APIs to work with content.
This is the 12th post in a seemingly never-ending series, where we will explore how to work with those
alternatives.
One apparent gap in the MediaStore options is the Documents/ directory. We
have obvious support for the media directories and Downloads/, but everything
else had to be handled via the Storage Access Framework. This includes Documents/, which,
like Downloads/, seems like it would be a general-purpose location.
As it turns out, there is support for Documents/. It���s just not documented.
This comes to us courtesy of Stack Overflow user blackapps, and
this answer.
To work with downloads, as we saw previously,
we can use MediaStore.Downloads as the basis. However, using
MediaStore.Files, we can work with Downlodads/ and Documents/, plus
custom subdirectories under those.
Specifically, MediaStore.Files.getContentUri("external") gets us a Uri
that we can use akin to MediaStore.Downloads.EXTERNAL_CONTENT_URI for inserting
new content and querying for our own content, though as before we cannot
access other apps��� content this way. If we use RELATIVE_PATH and specify
Documents, our content will go into the Documents/ directory:
val values = ContentValues().apply {
put(MediaStore.Files.FileColumns.DISPLAY_NAME, filename)
put(MediaStore.Files.FileColumns.MIME_TYPE, mimeType)
put(MediaStore.Files.FileColumns.RELATIVE_PATH, "Documents")
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
val resolver = context.contentResolver
val uri =
resolver.insert(MediaStore.Files.getContentUri("external"), values)
uri?.let {
resolver.openOutputStream(uri)?.use { outputStream ->
val sink = outputStream.sink().buffer()
response.body?.source()?.let { sink.writeAll(it) }
sink.close()
}
values.clear()
values.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(uri, values, null, null)
} ?: throw RuntimeException("MediaStore failed for some reason")
We can also use subdirectories under Documents/, such as Documents/AwesomeStuff.
And, we can use Downloads/ as the base. Attempts to write to other root directories
than Documents/ and Downloads/ will fail with an error.
You can even use this for removable volumes. blackapps��� answer shows a hard-coded
storage volume ID, though presumably using StorageManager and methods like
getStorageVolumes() will be more flexible.
The fact that this does not appear to be documented means that it is possible that
this will not be supported in future versions of Android. So, use this approach
with caution. But, if Documents/ is a must-have location, and you really want
to avoid the Storage Access Framework, MediaStore.Files may be something to consider.
The entire series of ���Scoped Storage Stories��� posts includes posts on:
The basics of using the Storage Access Framework
Getting durable access to the selected content
Working with DocumentFile for individual documents
Working with document trees
Working with DocumentsContract
Problems with the SAF API
A specific problem with listFiles() on DocumentFile
Storing content using MediaStore
Reading content from the MediaStore
Modifying MediaStore content from other apps
Limitations of MediaStore.Downloads
The undocumented Documents option
February 10, 2020
���Elements of Android Room��� Version 0.2 Released
Subscribers now have
access to Version 0.2 of Elements of Android Room,
in PDF, EPUB, and MOBI/Kindle formats. Just log into
your Warescription page to download it,
or set up an account and subscribe!
This update brings with it four new chapters, on:
Polymorphic entities
Using full-text search
Conflict resolution options
Using the Paging library with Room
Plus, there is your usual set of bug fixes and other minor tweaks.
The next update to this particular book is tentatively slated for April, as
I want to update some of the other books first. Next up: wrapping up the major
work on Elements of Android Jetpack.


