Mark L. Murphy's Blog, page 20
October 19, 2019
Scoped Storage Stories: SAF Basics
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 first post in a series where we will explore how to work with those
alternatives, starting with the Storage Access Framework (SAF).
The Storage Access Framework operates on the same principles of file-selection
UIs that users have been using for decades:
We have a way to ask the user to choose an existing file or other piece of content
(ACTION_OPEN_DOCUMENT)
We have a way to ask the user to choose where we can place a new piece of content
that our app will create (ACTION_CREATE_DOCUMENT)
We have a way to ask the user to choose an existing directory or other form of
���document tree��� that we can use for working with multiple documents and sub-trees
(ACTION_OPEN_DOCUMENT_TREE)
The first two have been available since Android 4.4; ACTION_OPEN_DOCUMENT_TREE
was added in Android 5.1. The vast majority of Android devices in use today have
access to these actions.
As these symbols��� names suggest, they are action strings for use in implicit
Intent construction. And, since we are going to be bringing up UI for the user
to choose things, we will use these Intent objects to start activities. In particular,
these Intent actions are designed for use with startActivityForResult(),
so we get the results of the user���s selection.
Choosing Via ACTION_OPEN_DOCUMENT
So, if you want to ask the user to choose a file or piece of content,
use ACTION_OPEN_DOCUMENT:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.setType("text/*")
.addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(intent, REQUEST_SAF)
Here, we use setType() to indicate the MIME type of the content that we are seeking
��� in this case, something that is text-based. Wildcard MIME types are fine,
but bear in mind that there is no absolute guarantee that the content that the user
chooses will be actually of that MIME type. The SAF content-chooser UI will try
to filter out incompatible stuff, but it has no way to know that some file named
this_is_not_text.txt is really some cat GIF that got renamed with a .txt file
extension.
The addCategory(Intent.CATEGORY_OPENABLE) part of the Intent configuration
says ���um, yeah, we���d really kinda like to actually work with this content���.
In particular, you should be guaranteed getting a piece of content that you can
open an InputStream on. You would think that this would be the default behavior,
but it is not, so we need to add the category to help ensure that things work
as expected.
Creating via ACTION_CREATE_DOCUMENT
ACTION_OPEN_DOCUMENT will let you read in existing content. ACTION_CREATE_DOCUMENT
will let you create new content. In desktop environments, ACTION_OPEN_DOCUMENT
is the ���file open��� dialog, while ACTION_CREATE_DOCUMENT is the ���file save-as���
dialog.
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
.setType("text/plain")
.addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(intent, REQUEST_SAF)
The code to make the request is basically the same, with two tweaks:
We use ACTION_CREATE_DOCUMENT
We use a concrete MIME type
In Android, the basic MIME type rules are pretty simple:
If you are requesting content from something, you may be able to use a wildcard
MIME type
If you are providing content to something, you need to use a concrete MIME
type, as it is your content and you need to be specifying what sort of content
it is
Getting the Result
Your startActivityForResult() call will eventually trigger an onActivityResult()
callback. There, if the result is RESULT_OK, you can get a Uri that represents
the chosen location:
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
if (requestCode == REQUEST_SAF) {
if (resultCode == RESULT_OK && data != null) {
data.data?.let { uri -> TODO("do something with the Uri") }
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
Reading From the Uri
We can then use a ContentResolver to read in any existing content at that
Uri, for the ACTION_OPEN_DOCUMENT scenario:
suspend fun read(context: Context, source: Uri): String = withContext(Dispatchers.IO) {
val resolver: ContentResolver = context.contentResolver
resolver.openInputStream(source)?.use { stream -> stream.readText() }
?: throw IllegalStateException("could not open $source")
}
private fun InputStream.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
You get a ContentResolver from any Context via its getContentResolver()
method (here mapped to a contentResolver Kotlin property). openInputStream()
will attempt to open an InputStream on the supplied Uri. That InputStream
works similarly to the FileInputStream that you might have used for a plain
Java File. Here, we read in all of the text from the content identified
by the Uri. This read() function will throw an exception, either on its
own (if openInputStream() returns null) or if something we call throws
an exception (e.g., openInputStream() might throw FileNotFoundException).
In this case, all of our work is wrapped in a CoroutineContext tied to
Dispatchers.IO, so we can get this I/O work onto a background thread supplied
by Kotlin���s coroutines system.
Writing to the Uri
For either ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT, you can write
content to the location identified by the Uri, using similar code:
suspend fun write(context: Context, source: Uri, text: String) = withContext(Dispatchers.IO) {
val resolver: ContentResolver = context.contentResolver
resolver.openOutputStream(source)?.use { stream -> stream.writeText(text) }
?: throw IllegalStateException("could not open $source")
}
private fun OutputStream.writeText(
text: String,
charset: Charset = Charsets.UTF_8
): Unit = write(text.toByteArray(charset))
Just as ContentResolver has openInputStream(), it has openOutputStream().
You get an OutputStream that you can use to write out content, such as by using
the writeText() extension function on OutputStream shown in the code snippet.
Uri Usage Rules
The Uri that we get from these actions will have a content scheme. In
general, with such a Uri, you do not want to make any assumptions about
it ��� treat it as an opaque identifier, nothing more.
The Uri is a bit like an HTTPS URL to a password-protected Web page: you have
limited time that you can access the content identified by the Uri. The
default behavior is that your activity that was responsible for the
startActivityForResult() call can use that Uri, but other components of
your app (other activities, services, etc.) cannot use it, and you cannot use
it after this activity instance is destroyed. An upcoming blog post in this
series will cover getting long-term access rights to the content, for cases
where you might need it.
What Else Is There?
Upcoming posts in this series will include:
How to get long-term access rights, as mentioned above
The role of DocumentFile
How to work with document trees from ACTION_OPEN_DOCUMENT_TREE
And more!
October 11, 2019
The Storage Situation: Removable Storage
Back in 2014, I wrote a series of blog posts to try to clear up confusion
around where you can read and write files in Android. I updated them in
2017 to reflect changes in Android��� but Android keeps changing.
This post is an updated
edition of the 2017 post on removable storage.
Working with files on the filesystem in Android is seriously confusing.
It was confusing in 2008, and it has only gotten more confusing as the years
rolled on. There
are countless Stack Overflow questions and the like where they clearly do not
quite grok the various pieces of Android���s storage model.
This is the third post in a series covering this storage model, to help
clarify what is going on. Earlier, we looked at
internal storage and
external storage.
Today, we will look at removable storage, the source
of an unfortunate amount of angst.
What Your Users Think ���Removable Storage��� Means
Many of your users will have a device that has some sort of removable media.
Often times this is a micro SD card. Some tablets or docks have a full SD card slot.
Plus, USB mass storage is possible via USB On-The-Go and USB Type C connectors (not to mention
devices or docks with a full USB host port).
Your users will think that they can work with removable storage much like they
can with a desktop or notebook.
Unfortunately, your users are largely mistaken, and are even more mistaken
with Android 4.4+. That���s because Google���s approach towards removable storage is���
unconventional.
What Google Thinks ���Removable Storage��� Means
In the beginning, external storage was often in the form
of a removable micro SD card. At that time, many developers got in the habit of
thinking that external storage == removable storage.
However, as Android 3.0 and higher started rolling out en masse, developers soon
started to realize two things:
External storage != removable storage on most of those devices
There���s nothing in the Android SDK for removable storage
Wait, Wut?
That���s right: until Android 4.4, there was no official support for removable media
in Android. Quoting Dianne Hackborn:
���keep in mind: until Android 4.4, the official Android platform has not supported SD cards at all except for two special cases: the old school storage layout where external storage is an SD card (which is still supported by the platform today), and a small feature added to Android 3.0 where it would scan additional SD cards and add them to the media provider and give apps read-only access to their files (which is also still supported in the platform today).
Android 4.4 is the first release of the platform that has actually allowed applications to use SD cards for storage. Any access to them prior to that was through private, unsupported APIs. We now have a quite rich API in the platform that allows applications to make use of SD cards in a supported way, in better ways than they have been able to before: they can make free use of their app-specific storage area without requiring any permissions in the app, and can access any other files on the SD card as long as they go through the file picker, again without needing any special permissions.
But��� But��� But��� What About All These Apps That Used Removable Media?
They fall into three buckets:
Some are just relying on MediaStore indexing. So, for example, a video player can
find out about videos on all available media by querying the MediaStore, and if
the device manufacturer hasn���t broken the MediaStore indexing of removable media,
the player will be able to play back videos on removable media. This strategy is
broken on Android 10 and higher.
Some are apps that ship with the hardware. The hardware manufacturer knows the
device and what the rules of the game are for that device. The hardware manufacturer
is also far less concerned about cross-device compatibility, as their apps aren���t
(usually) shipping on the Play Store. Hence, a hardware manufacturer has carte blanche
to work with removable media.
Some are apps written by developers who decided to go past the boundaries of
the Android SDK. There are various recipes online for examining various Linux
system files (and file-like substances) to determine what ���mount points��� exist,
and from there apply some heuristics to determine what represents removable media.
While reliability across devices could easily be an issue, beyond that, these
techniques at least sorta worked��� until Android 4.4, when everything changed.
What Happened in Android 4.4
Starting with Android 4.2, there was a request from Google for device manufacturers to
lock down removable media. Generally, this was ignored.
For Android 4.4+, Google amended the Compatibility Test Suite (CTS) that device manufacturers
must comply with in order to ship a device containing Google���s proprietary apps
(e.g., Play Store, Maps, Gmail; otherwise known as ���GMS���). Quoting
Dave Smith:
However, new tests were added in CTS for 4.4 that validate whether or not secondary storage has the proper read-only permissions in non app-specific directories, presumably because of the new APIs to finally expose those paths to application developers. As soon as CTS includes these rules, OEMs have to support them to keep shipping devices with GMS (Google Play, etc.) on-board.
As a result, apps were able to read files on removable media using the various undocumented
and unsupported tricks for finding removable media. However, apps cannot write to or
otherwise modify such removable storage. Note that device manufacturers themselves
may have ways of dealing with this, but ordinary app developers do not.
However, we were given getExternalFilesDirs() and getExternalCacheDirs() in
Android 4.4. Android 5.0 added getExternalMediaDirs(). These methods give us
locations on external and removable storage into which our apps can write. We
do not need any permissions to use them ��� not even the runtime permissions from
Android 6.0. However, the filesystem locations are a bit user-hostile and the content
that we place in those directories will be removed when the app is uninstalled.
What Happened in Android 10
Removable storage, like external storage, was largely locked down. We still
have access to the locations from the aforementioned methods. However, any other
���back door��� approaches that developers used were completely shut down.
This blog post
has much more detail on what Android 10 did to external storage, and all that
also applies to removable storage.
Should I Hardcode Paths in My App?
No.
Other Posts in This Series
This series includes:
A post on internal storage
A post on external storage
This post on removable storage
October 10, 2019
Spoiler Alert!
Every weekday, as part of maintaining AndroidX Tech, I
round up all of the new and updated artifacts that are available on Google���s
Maven repo (those that begin with androidx., anyway).
These showed up this morning:
androidx.compose:compose-compiler:0.1.0-dev01
androidx.compose:compose-runtime:0.1.0-dev01
androidx.ui:ui-core:0.1.0-dev01
androidx.ui:ui-layout:0.1.0-dev01
androidx.ui:ui-framework:0.1.0-dev01
androidx.ui:ui-animation:0.1.0-dev01
androidx.ui:ui-animation-core:0.1.0-dev01
androidx.ui:ui-android-text:0.1.0-dev01
androidx.ui:ui-material:0.1.0-dev01
androidx.ui:ui-tooling:0.1.0-dev01
androidx.ui:ui-platform:0.1.0-dev01
androidx.ui:ui-vector:0.1.0-dev01
androidx.ui:ui-foundation:0.1.0-dev01
androidx.ui:ui-text:0.1.0-dev01
androidx.ui:ui-test:0.1.0-dev01
In other words, Jetpack Compose has arrived, in an early pre-release form.
Though, as it turns out, not really. The androidx.compose artifacts appear
to be empty, other than a META-INF/MANIFEST.MF file in each.
Ian Lake did not seem to mention Compose in his release tweetstream.
That, and their empty contents, means that is possible that these androidx.compose artifacts were accidentally put into
the Maven repo.
A quick skim of the androidx.ui artifacts shows that they do have
code. However, they depend upon androidx.compose:compose-runtime, and
since that is empty, it is unclear if the androidx.ui artifacts will work either.
So, I do not know what to make of these artifacts. I and others will experiment with
them, and if we can get them working somehow, we���ll be writing about it.
October 8, 2019
The Storage Situation: External Storage
Back in 2014, I wrote a series of blog posts to try to clear up confusion
around where you can read and write files in Android. I updated them in
2017 to reflect changes in Android��� but Android keeps changing.
This post is an updated
edition of the 2017 post on external storage.
Working with files on the filesystem in Android is seriously confusing.
It was confusing in 2008, and it has only gotten more confusing as the years
rolled on. There
are countless Stack Overflow questions and the like where they clearly do not
quite grok the various pieces of Android���s storage model.
This is the second post in a series covering this storage model, to help
clarify what is going on. Yesterday, we looked at
internal storage.
Today, we will look at external storage.
What Your Users Think ���External Storage��� Means
Many Android device users will have no idea what ���external storage��� means.
There is nothing in the device UI that will necessarily use that term.
At best, your users will think that an SD card is external storage.
That���s not quite right. Except on Android 4.4+, in which case it is only
partially right.
Did I mention that this stuff is confusing?
What Google Thinks ���External Storage��� Means
The Android SDK documentation
has this to say in terms of a definition of ���external storage���:
Every Android device supports a shared ���external storage��� space that you can use to save files. This space is called external because it���s not guaranteed to be accessible���it is a storage space that users can mount to a computer as an external storage device, and it might even be physically removable (such as an SD card).
In the halcyon days of yesteryear,
there was a single volume known as ���external storage���, and it was effectively defined as
���the stuff that shows up when the user plugs their device into a computer using a
USB cable���. Even that wasn���t completely accurate, as some manufacturers would also
allow access to their devices��� removable media via the USB cable as well. And
Android 4.4 added yet more wrinkles in terms of removable media��� which is why
removable media gets its own blog post tomorrow. Android 10 further ���upset the apple cart���
with all of this.
For the purposes of this blog post ��� and to line up with what most other written
material will refer to ��� ���external storage��� is defined as the directory tree returned
by Environment.getExternalStorageDirectory().
The Many Paths Under Which External Storage is Stored
For us as developers, the actual path under which this magical ���external storage��� was
accessed varied over the years, from /sdcard, to locations under /storage, to
/mnt/shell/emulated/0, then back to locations under /storage (e.g., /storage/emulated/0).
And just as secondary users of an
Android 4.2+ tablet get their own internal storage, they get their own external storage,
with its own root directory.
Hence, as I mentioned previously:
NEVER HARDCODE PATHS
Use various methods to get the base directory to work from��� though this too
has gotten more complicated over the years.
The Many APIs for Finding External Storage Locations
In the beginning, everyone used Environment.getExternalStorageDirectory(),
which pointed to the root of external storage. This led to external storage
being just a big basket of random content.
Later, Google offered more organization:
getExternalFilesDir() and getExternalCacheDir() on Context, pointing
to an application-specific directory on external storage, one that would be
deleted when the app is uninstalled
Environment.getExternalStoragePublicDirectory(), for centralized places
to store well-known file types, like photos and movies
Note that the Context methods have plural forms on Android 4.4+
(getExternalFilesDirs() and getExternalCacheDirs()), which ties into removable
media, which we will get into more tomorrow.
And note that on Android 10, the methods on Environment are officially deprecated
and are no longer accessible by default ��� more in this in a bit.
External Storage and Permissions
Just as the location ��� physically and logically ��� of external storage keeps
changing, so does our ability to work with it.
Originally, apps could do whatever they wanted.
Android 1.5 added the WRITE_EXTERNAL_STORAGE permission, which apps had to hold
to be able to write files to external storage. That way, the user would be informed
at install time that the app intended to modify external storage contents. Any app
could still read from external storage, though.
Android 4.4 started enforcing a READ_EXTERNAL_STORAGE permission, so you cannot
even read from external storage if you do not hold some permission. Note that
WRITE_EXTERNAL_STORAGE implies READ_EXTERNAL_STORAGE, so you only need one of those,
not both. And, for getExternalFilesDir() and getExternalCacheDir(), you do not
need either of those permissions ��� you can read and write in those directories
with impunity. Android now has an android:maxSdkVersion attribute on
<uses-permission>, specifically so that you can drop WRITE_EXTERNAL_STORAGE if
you no longer need it, because you are only working with getExternalFilesDir()
and getExternalCacheDir().
Android 6.0 made developers have to start asking for READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE have a
via runtime permissions (e.g., checkSelfPermission(), requestPermissions()).
That is because these permissions have a protectionLevel of dangerous.
What Android 10 Did
By and large, external storage is no longer accessible by default.
You still have access to getExternalFilesDir(), getExternalCacheDir(),
and getExternalMediaDir() methods on Context and their associated filesystem
paths. You do not need any permissions to work in those directories. However,
they are in an inconvenient location for the user (Android/data/.../files/, where
... is your application ID). Plus, the content in those directories is removed
when your app is uninstalled.
While there are ways of working around this limitation for Android 10, those
workarounds are slated to be removed with 2020���s Android release.
This blog post
has much more detail on what Android 10 did to external storage.
Poking Around External Storage
As a developer, assuming that you can find where external storage really resides
for your version of Android, you have unfettered access to it from the Device
File Explorer in Android Studio, for both emulators and production devices.
You can also use a USB cable, much like your users will use. However, bear in mind
that what is presented to the USB interface is not what is on external storage���
but, instead, is what has been indexed on external storage in the MediaStore.
Hence, unless you take steps
to ensure that new files that you create get indexed, they may not be immediately visible.
Under the covers, Android is using the Media Transfer Protocol for its USB
communications. This enables a lot more flexibility than does Mass Storage Mode
(a.k.a., what thumb drives use) that Android used originally. However, some MTP
clients may cache directory listings, so even after you get your file
indexed by MediaStore, an already-attached client may need to be refreshed.
How Do I Secure My Files on External Storage?
In general, you don���t. The user has complete access to external storage. Also, other
apps can get to external storage if they hold the right permissions or the user
chooses to grant them access to content (e.g., via ACTION_OPEN_DOCUMENT from
the Storage Access Framework).
One thing that you can do is use something like Facebook���s Conceal.
This encrypts your files on external storage, but uses a generated encryption
key kept on internal storage. From a security standpoint, the net effect
is to make external storage closer to internal storage in terms of read access.
Note, though, that Conceal cannot prevent other apps, or the user, from deleting
your files on external storage, or perhaps trying to write to them and corrupting
the files as a result.
Should I Hardcode Paths in My App?
No.
Other Posts in This Series
This series includes:
A post on internal storage
This post on external storage
October 6, 2019
The Storage Situation: Internal Storage
Back in 2014, I wrote a series of blog posts to try to clear up confusion
around where you can read and write files in Android. I updated them in
2017 to reflect changes in Android��� but Android keeps changing.
This post is an updated
edition of the 2017 post on internal storage.
Working with files on the filesystem in Android is seriously confusing.
It was confusing in 2008, and it has only gotten more confusing as the years
rolled on. There
are countless Stack Overflow questions and the like where they clearly do not
quite grok the various pieces of Android���s storage model.
This is the first post in a series covering this storage model, to help
clarify what is going on. Today, we will look at ���internal storage������ including
its various definitions.
What Your Users Think ���Internal Storage��� Means
Some of your users will not recognize ���internal��� as a qualifier on ���storage���.
At best, they will think that ���internal storage��� represents all of the on-board
flash. For example, users may still see ���internal storage��� in places like an Explorer
window in Windows, when their device is connected via USB.
What Google Thinks ���Internal Storage��� Means
Alas, that is not what the Android SDK thinks ���internal storage��� means, which is
where some of the confusion lies.
If you read the Android documentation on internal storage,
they never actually bother to define the term.
In truth, the Android SDK���s definition of ���internal storage��� is a specific
directory, unique to your app, where your app can place files. Those files are
read-write for your app, but no other apps will have access to those files.
(exception: users running file managers with superuser privileges on rooted devices
can access anything)
There are a handful of methods on Context that give you access to
particular locations on internal storage, including:
getCacheDir()
getDir()
getDatabasePath()
getFilesDir()
openFileInput()
openFileOutput()
Other methods will rely upon these, such as openOrCreateDatabase(). Other
classes also will rely upon these, such as SQLiteOpenHelper and
SharedPreferences.
Where Internal Storage Is Stored��� Sometimes
If you look around various blog posts, StackOverflow answers, and books that
came out in 2012 or earlier, you will be told that your app���s ���internal storage���
resides at:
/data/data/your.application.package.name
(where your.application.package.name is replaced by your application ID, as is
declared in the package attribute in the manifest or modified via Gradle)
Inside of there will be some directories automatically created by Android as
you use some of those Context methods. For example, getFilesDir() returns a
File object that points to a files/ directory inside of your app���s
internal storage.
Where Internal Storage Is Stored��� The Rest of the Time
However, this is not always where your app���s internal storage resides. If there
is one rule for developers that you should take away from this blog post series,
it is:
NEVER HARDCODE PATHS
Every now and then, I will see developers do something like this:
File f=new File("/data/data/their.app.package.name/files/foo.txt&...
This is not a good idea.
First, it is more typing than using getFilesDir():
File f=new File(getFilesDir(), "foo.txt");
More importantly, internal storage is not always at the same place. Notably,
we have the notion of separate user profiles, starting in
Android 4.2 for
tablets and Android 5.0 for phones.
Each user gets his or her own ���internal storage��� area. While the aforementioned
directory is still used for the primary user, that is not guaranteed, and it will
not be used for secondary accounts.
Poking Around Internal Storage
The Device File Explorer tool in Android Studio 3.0+ can browse all of internal
storage on an emulator, plus internal storage of debuggable apps on production
devices. This should handle most of your scenarios:
[image error]
From the command line, you can use the
run-as option with adb at the command line. For example, to download a
database from the primary user���s internal storage to your development machine, you
might use:
adb shell 'run-as your.application.package.name cp /data/data/your.application.package.name/databas... /sdcard'
Note that:
You will need to change the destination to wherever on your device external
storage is mapped to (shown here as /sdcard/, which will not work on all devices)
You may need to use cat instead of cp on older devices
Once the file is on external storage, you can use adb pull to download
it to your development machine, or access it by other conventional means
(e.g., via mounting the device as a drive on your development machine).
Limitations of Internal Storage
On ancient Android 1.x and 2.x devices, internal storage was usually on a dedicated
filesystem partition, and that partition was usually rather tiny. The HTC Dream
(a.k.a., T-Mobile G1), the original Android device, had a whopping 70MB of
internal storage for use by all apps.
(And, no, that���s not a typo. We measured storage in megabytes back then. We also
had onions on our belts, as that
was the style at the time.)
Nowadays, internal storage usually is substantially larger: 32GB, 64GB, 128GB,
and so on.
Frequently-Asked Questions
Here are some FAQs about what the SDK refers to as internal storage:
Should I Make Files on Internal Storage World-Readable or World-Writable?
No. That is no longer an option on modern versions of Android, and it was never
a particularly good idea.
Use FileProvider and serve that content via that
ContentProvider implementation. Then, you at least have the option of using
Android���s permission system to manage access to those files, versus having
any app on the system be able to monkey with those files.
Well, How About android:sharedUserId?
I also counsel against this.
android:sharedUserId is an attribute you can place in your manifest that indicates
a logical user ID to be used for your app. Any other app that is installed that
is signed by the same signing key and requests the same android:sharedUserId
will use the same Linux user from a security standpoint. The net effect is that
those two apps will be able to work with each other���s files with impunity, as those
files are all owned by the same Linux user.
This attribute is really designed for pre-installed apps, such as some software
suite pre-loaded by the device manufacturer, carrier, or ROM mod maintainer. In
particular, once you ship your app, you cannot reliably change your android:sharedUserId
value without locking your user out of any existing files��� as Android does not
change the ownership of existing files when it changes the Linux user account that
your app runs as.
There are various risks in having multiple processes work simultaneously with
files. Some subsystems, like SQLite, have built-in logic to deal with this.
But if you are doing your own file access yourself (e.g., via File and Java I/O), then
you have to somehow deal with simultaneous access, which can get tricky.
You also have to deal with what happens when one app is uninstalled, taking away
files that another app was using. In a hub-and-spoke model, like an app and a suite
of plugins, perhaps this is not that risky. In other models, where apps are closer
to being peers, you cannot afford to have your app���s data vanish because the user
elected to uninstall some separate app.
Finally, you do not know what the future might bring. Right now, you might view
your set of apps as a tightly-coupled suite. Somebody who acquires those apps,
or acquires your firm, might wish to go a different route. Using data sharing
options that are more loosely coupled, like a ContentProvider, gives you greater
flexibility than does assuming that your apps should co-mingle their files.
In an ideal world, your app should treat other apps as a fairly-reliable but not
always available resource, just like you treat your own Web service.
Besides, android:sharedUserId was deprecated in Android 10 and is slated to be removed
from the OS in a future release.
How Do I Prevent Users of Rooted Devices From Accessing My Files on Internal Storage?
Simple: don���t put those files on internal storage. Users of rooted devices can access
whatever they want on the device, so the only way to prevent them from accessing your
data is to not have it on the device.
Some developers will attempt to encrypt their files, using a hard-coded password,
to prevent rooted device users from using those files. This does erect a ���speed bump���,
but it is a small one. All it takes is one person with interest to reverse-engineer
your app, determine how to decrypt those files, then write up a blog post or
discussion board entry on how to do it.
On the whole, there are relatively few people with rooted devices ��� I estimate it
at well under 1%. IMHO, you will have better success by focusing your
engineering work on writing a better app, rather than spending that time trying
to block rooted device users.
Did Any of Those Android 10 Changes Affect Internal Storage?
No. You can work with internal storage on Android 10 the same as you have in
other recent Android versions.
Should I Hardcode Paths in My App?
No.
September 22, 2019
View Binding and Android Studio Versions
The documentation for view binding
states:
Note: View binding is available in Android Studio 3.6 Canary 11+.
That���s not strictly accurate.
Things like view binding are not part of Android Studio. If they were, we could
not use view binding in projects that are built from the command line or from
a CI server.
The more accurate description is:
Note: View binding is available in Android Studio 3.6 Canary 11+, once you upgrade to 3.6.0-alpha11 or higher of the Android Gradle Plugin
For a new project created in Android Studio 3.6, you will get the corresponding
Android Gradle Plugin line with a version matching that of Android Studio. But,
if you open an existing project, you may have an older Android Gradle Plugin
version in your Gradle scripts.
The real requirements for using view binding are:
Use 3.6.0-alpha11 or higher of the Android Gradle Plugin:
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0-alpha12'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Use Gradle 5.6.1 or higher, as that is required by this version of the Android Gradle Plugin
Use Android Studio 3.6 Canary 11+, as Android Studio 3.5 (or older) will not open a project using 3.6.0-alpha11 or higher of the Android Gradle Plugin
For solo-developer projects, making these changes is easy. For team projects, everybody
will need to switch to Android Studio 3.6 Canary 11+, which may or may not be practical,
depending on your team���s stance on pre-release tools.
September 9, 2019
���Elements of Android Jetpack��� Version 0.5 Released
Subscribers now have
access to Version 0.5 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 two new chapters:
���Inverting Your Dependencies���
It also updates everything for Android Studio 3.5, adds some Android 10 notes,
updates many of the dependencies used in the book, and fixes lots of bugs.
August 22, 2019
Introducing AndroidX Tech
AndroidX Tech (https://androidx.tech) is a site that
I am maintaining that contains additional
information about the various androidx libraries, beyond what is offered
in the official documentation.
It arose from a variety of frustrations when trying to use these libraries and classes.
For example, earlier this week, I wanted to know when saved-state support was
added to the ViewModel classes. I knew that it involved
the SavedStateHandle class,
but the official documentation does not show the artifact that this class resides
in, let alone any version information about that artifact (e.g., when was this
class added?). In some cases, the release notes help, and a search of
the lifecycle release notes
would turn up some information. That���s not a reliable solution, though, as not
everything gets mentioned in release notes.
Instead, I went to the AndroidX Tech page for the androidx.lifecycle package.
There, every class that has appeared in that package is listed, along with
the artifact and versions. There, we can see that SavedStateHandle was
introduced in androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha01
and the most recent version is androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha03.
In addition to lists of classes and their associated artifacts, I have:
Pages per artifact, including:
Links to the release notes
Links to details about each artifact version
Links to documentation, articles, samples, and so forth, that I am manually collecting (though I���m only a tiny way through all of the artifacts at this point)
Some notes about the role of the artifact, for artifacts that you are likely to use directly
Pages per artifact version, including:
The release date
The SHA-256 checksum of the AAR that I am seeing
The minimum and target SDK version information
Any permissions that the artifact requires
The transitive dependencies that the artifact requires
Links to each of the source files found in that artifact version
Formatted source for every source file from every artifact version
Blog-style posts for new artifacts and versions that I am seeing, including an Atom feed
In addition to building out the manually-curated content and updating the
site for new artifacts and versions, I am still working on the site layout.
The site is built using MkDocs, which is a nice tool
that does not scale to thousands of pages very well. So, I will be rebuilding this
in some other static site generator eventually, though I will be keeping the
existing URL structure.
If there are other bits of information that you think would be useful on this
site ��� whether it is automatically built from the AARs and POMs or manually added ���
let me know!. Similarly, if you encounter any
problems with the site, tell me!. If you have
content about AndroidX that you would like to contribute,
the curated content GitLab repo has
details about that.
August 21, 2019
Android Studio 3.5 Upgrade XML Reformatting Problems
If you upgrade Android Studio 3.4 to 3.5, you may run into a problem where
reformatting an XML file (e.g., via Ctrl-Alt-L) reorders the XML elements in
each level of the hierarchy. So, for example, you might have a <uses-permission>
before <application> in your <manifest> element; reformatting would flip
the order of those elements. It seems to be alphabetizing them.
For the manifest, this might almost be survivable. However, lots of things in
Android are dependent upon XML element order, such as the children of a LinearLayout,
the children of <menu>, the children of <layer-list>, and so on.
The original issue for this appears to be this one.
It is marked as ���fixed���, though that appears to be overly optimistic. A number
of developers are running into the problem, filing other issues and
asking Stack Overflow questions.
I ran into it myself in my ���daily driver��� Android Studio setup.
Based on some experiments, the problem seems to be limited to upgraders.
I cannot reproduce the effect with a fresh Android Studio 3.5 installation.
The simple fix is outlined in the aforementioned Stack Overflow question:
Go into the Settings screen (File > Settings, or Apple > Settings, depending on your OS)
Navigate into Editor > Code Style > XML
In the upper-right of that dialog, choose Set From > Predefined Style > Android
The real problem lies in the ���Arrangement��� tab in that XML code style screen.
After an upgrade from 3.4 to 3.5, you wind up with arrangement rules like these:
There may be a more surgical fix that can be applied to those rules, editing
each of them to limit them to attributes, but I have not tried this. Applying
the Android predefined style replaces the buggy post-upgrade rules with valid
rules:
Because the problem lies with an upgrade, it might be tricky for Google to fix
it with yet another upgrade to some future 3.5.1 version. With luck, they���ll find a solution.
Regardless, when you upgrade to Android Studio 3.5 from 3.4, double-check these
arrangement rules and fix them if they appear broken.
Many thanks to Birju Vachhani for his writeup of the steps to fix the problem!
August 19, 2019
���Elements of Kotlin Coroutines Version 0.1 Released
Subscribers now have
access to Version 0.1 of Elements of Kotlin Coroutines,
in PDF, EPUB, and MOBI/Kindle formats. Just log into
your Warescription page to download it,
or set up an account and subscribe!
Originally, I thought that I would just have a chapter on basic coroutines in
Elements of Kotlin. However, I quickly realized that to cover enough
for Android app developers, I would need a lot more than just one chapter.
Elements of Kotlin Coroutines is the result.
As with all my 0.x editions, this one is partially complete, with a number
of placeholders for future chapters. However, it covers the basics of coroutines,
including suspend functions and Flow. It also includes a ���hands-on��� appendix
that walks you through converting a small RxJava-based app to use coroutines.
And most of the samples are in the Klassbook,
so you can run and modify them from the comfort of your favorite Web browser.
This book will get updated every month or two for a while, as I flesh out the
remaining material.


