[MUSIC PLAYING]
YACINE REZGUI: Hi.
My name is Yacine Rezgui, and I'm
part of the Developer Relations Engineering team
on Android, working on privacy.
Today, I'll tell you everything you
need to know about storage on Android, which APIs to use,
and the best practices you should follow.
Let's first have a look at how storage
is architected on Android.
A device has a primary storage volume stored on flash memory
or on the hard drive where the OS and apps are located.
When Android installs an app, it creates a directory
for the app's internal storage on the same primary storage
volume.
It's private and accessible only to that app,
so it's the right place to store anything
confidential, like databases, configuration files.
Android also creates a directory for the app's external storage.
It's another location to save app-specific files.
Some devices may have limited space available
on the primary volume, thus making
the app external storage only available
through a micro SD card.
As the external storage can be removable,
you always have to check its availability before using it.
Keep in mind that, before Android 11,
the app's external storage is readable and writable
by other apps that have the right storage permissions.
So don't store any sensitive information there.
Both the app internal and external storage directories
are deleted when the app is uninstalled.
And finally, there's the shared storage, also known
as the device external storage.
It's a common location for all apps
to contribute files that can be seen, file managers,
and photo gallery apps.
Files added there aren't deleted after apps are uninstalled.
The shared storage content is automatically
indexed by MediaStore, which is a content provider that
keeps track of metadata for shared storage files.
It's used by apps to efficiently fetch
information on all media files.
These different types of storage locations
can change on different Android versions.
So if you're using file paths, don't
save absolute path references to files,
but instead, keep relative references
from the root of these different storage locations.
Now we'll look into scoped storage.
Up to Android 9, files in the shared storage
were readable and writable by apps that requested
the right permissions.
On Android 10, we released a major privacy upgrade
regarding shared files access named scoped storage.
When an app request read external storage
on this version, access to shared storage
is restricted to media files like photos, videos, and audio
files.
Any other files like PDF, ZIP, or DOCX,
known as document files, are accessible using
the document picker, which leverage the storage access
framework.
The document picker allows the user
to retain full control on which document files they give access
to.
We also introduced other changes to further improve privacy
and the developer experience.
WRITE_EXTERNAL_STORAGE is deprecated,
and adding files to the shared storage
does not require any permission.
Requesting explicit user consent with a dialogue shown to them
when modifying or deleting a media file is now required.
And finally, location metadata is removed for media files
automatically.
And to access them, you need to request access media location
permission.
Now we'll deep dive into common storage use cases.
Let's say our app downloads some user data in a config
file that you want to keep private.
First, we'll use OkHttp.
It's a popular open source library
to create and execute our Https request.
We'll create our client, and now we'll get to the response.
Once we get the response back, we
double-check that it has a response body.
We rely on the .use method from the Kotlin standard library
to close the underlying network socket automatically at the end
of the lambdas to avoid any memory leaks.
Finally, we save the request body into a file.
As the file content is private, we'll
save it into our app's internal storage using
the context.filesDir property.
To streamline our memory usage, we'll
copy the request body content into our file
by using streams instead of holding it entirely in memory.
The Kotlin standard library adds the .copyTo extension function
to input stream instances to facilitate the copy towards
output streams.
Now, let's imagine that we need to download
some large assets in our app that aren't confidential,
but are meaningful only to our app.
So we would like to avoid polluting the shared
storage with it.
We would follow the same instruction
as we did in our previous example,
saving the big file to the internal storage
as is the most available and performant storage location.
Some devices may have limited space available
on the primary volume, so we should check first
if there is still some enough space there.
If there is not enough space, we use the app's external storage
directory.
We should rely on the .getExternalFilesDirs method,
as the device may have extra volumes, like a micro SD card.
Finally, if there's still not usable space,
let's show a notice to the user explaining the situation.
Now, our app needs to add files to the shared storage
so the user can access them through other apps.
If we want to add an image, we need
to request the right external storage permission up
to Android 9.
On more recent devices, we don't need this permission anymore
to contribute files into the shared
storage, thanks to scope storage changes
introduced in Android 10.
For images, we will choose the pictures folder returned
by the environment, .getExternalStor
agePublicDirectory method, using the
Environment.DIRECTORY_PICTURES constant.
If we're adding a video, we will choose
the movies constant folder.
If it's a PDF file, we will choose the download folder,
et cetera.
Now that we copied the image into a new file,
we need to scan the file using media
scanner by providing its path and its MIME type.
It will index the file metadata inside MediaStore,
making it visible when apps query this content provider.
For the best performance and access
to more features like favorite, trashed, and pending files
on Android 10 and above, we recommend
using the MediaStore API to add files on the shared storage
instead of relying on the file API.
Check out our documentation for more details.
Now let's go to another use case.
Let's say we need to access document files,
so we will rely on the document picker via the action
OpenDocument intent.
First, let's add the Jetpack Activity
dependency to our project if it's not there already.
After that, we register our OpenDocument activity result,
which will handle the intent resolve logic.
If the user has selected a file, we'll
have a URI that we can open using the content resolver.
Finally, we launch our intent handler
with the MIME types we want to filter on.
And in this case, it's a PDF.
The action OpenDocument intent is
available on devices running Android 4.4 and higher.
To access photos and videos on the shared storage on version
up to Android 12L, you had to request read external storage
and implement your own UI for the user
to select the different items.
But wouldn't it be better to just use
something simpler for developers and privacy-friendly for users?
Say no more.
In Android 13, we introduced the photo picker,
a privacy-forward way to select for images and video.
It doesn't require any runtime permission
and has a better UX to access photos and videos.
It's maintenance-free, and you can even limit
the number of selectable items.
You can integrate it in your code
in just a few lines of code, and it's backported
to Android 11 and 12.
In the upcoming months, cloud media providers,
like Google Photos, will also be supported.
The photo picker will seamlessly include
backed up photos and videos to the selection grid,
even if they are not saved on your device.
Users will even see their custom and shared
albums in the Albums tab.
To use it, let's add the Jetpack Activity dependency version
1.6 and higher to our project if it's not there already.
After that, we register our PickVisualMedia activity
result, which will handle the intent result logic.
It will use the photo picker when available on the device
and fall back to the document picker for older devices.
If the user has selected a file, we'll
have a URI that we can open using the content resolver.
Finally, we launch our intent handler with a filter--
image and video, image only, video only,
or specific MIME type we want to filter on.
And that's it, three lines of code
to select an image or video without permissions.
If you want to keep access to a file
even after your app is closed, you
can call the takePersistableUriPermission
to be able to keep access to them, even after reboot.
Your app may have a use case requiring this broad access,
like gallery photo backup.
For these specific usages, we've introduced new permissions
in Android 13 when targeting API 33 that provide access
to specific types of media files,
including images, video, or audio.
You can read more about them in the documentations.
Privacy and transparency have been tremendously
improved over the recent releases of Android,
with even UX enhancements like the photo picker.
We're working on adding more APIs that
keep the user in control of giving access
while removing the need to request permissions on app
sites.
And lastly, as a recommendation for apps,
rather than relying on file paths, which only offer access
to local files and can be changed
over different versions of Android,
we're recommending to use URI to access files on Android.
Have a look at our documentation for more details
on the features I've presented today.
Thank you.
[MUSIC PLAYING]