External storage permissions on Android
The permission model introduced in Android 6.0 offers a new layer of security for users. Some actions have been refined to be more secure without requesting dangerous permissions to interact with files. For instance, using the right intent action for firing up the Android SAF picker does not even require the storage permissions. But why?
In this article, we’ll delve into the Android permission model — in particular, the storage permissions. We’ll have a look at how permissions work, starting from a high level and working our way down to the lower level managed by FUSE, the native Android module that interacts with the file system.
The dawn of the new permissions era
The main purpose of the Android permission model is to provide final users with a better understanding of which resources an app is going to use. If a device is running Android 6.0 (API level 23) or higher and the app’s targetSdkVersion
is 23 or higher, the user can refuse a specific permission. But if an app is well implemented, it should work even without accessing the resource that has been prohibited. That said, an app that must work without a resource uses a different implementation flow, and it is the responsibility of professional developers to make sure the user won’t experience crashes or strange behaviors.
The four protection levels
There are four protection levels that affect third-party apps: normal, signature, dangerous, and special permissions.
Using the adb
tool, we can list all the permissions. Adding the -d
and -g
options, we’ll list only dangerous permissions organized by group:
adb shell pm list permissions -d -g
To grant or revoke a single permission for an app, use the following:
adb shell pm [grant|revoke] <package-name> <permission-name>
You can grant as many permissions as you like by using this command multiple times.
✅ Normal permissions
Normal permissions are permissions that are not considered harmful. Examples of this include the permission to set the time zone. Normal permissions are granted at installation time, and the only precondition is declaring them in the manifest of the app.
⛔️ Signature permissions
Signature permissions are generally permissions defined by one app and used by another. The system grants these app permissions at install time only if the requesting application is signed with the same certificate as the application that declared the permission.
💀 Dangerous permissions
Dangerous permissions could potentially expose the user’s private information or share operations with other apps. For example, the ability to read the user’s contacts is a dangerous permission. Dangerous permissions are granted at runtime, and the user must explicitly grant the permission to the app.
🦄 Special permissions
Special permissions are a small set of permissions that are particularly sensitive, so most apps should not use them. Examples of these include SYSTEM_ALERT_WINDOW
and WRITE_SETTINGS
. Special permissions must be declared in the manifest, and the app will send an intent requesting the user’s authorization by showing a detailed management screen to the user.
Storage permissions
Storage permissions are dangerous permissions for accessing the shared external storage. Full read and write access to any location of the volume is protected by two permissions marked as dangerous: READ_EXTERNAL_STORAGE
and WRITE_EXTERNAL_STORAGE
.
When an app is granted storage permission, it can access the device storage at any time. This means it can upload personal files or even delete sensitive information from the device, so it’s better to think twice before giving storage permission to untrusted apps, as it can be harmful.
Only when the external storage is mounted and the permissions are granted will Android let you call Environment#getExternalStorageDirectory()
.
Calling a method that requires storage access without the right permissions will throw the exception java.lang.SecurityException
.
Accessing storage volume without storage permissions
There are some special paths that can be accessed without reading and writing permissions that are particularly useful for storing app private data: Context#getExternalFilesDir(String)
, Context#getExternalCacheDir()
, and Context#getExternalMediaDirs()
.
Another way to access a specific file without requiring dangerous permissions is by relying on the Android Storage Access Framework (SAF) picker.
The SAF picker does not allow an app to have full control of the storage, is much more restricted, and requires some interaction with the user to choose the right location: This can be a new file name to save or a specific document to open. There is also a special case where the SAF picker can open an entire directory using the intent action ACTION_OPEN_DOCUMENT_TREE
.
The outcome of the SAF picker will be a Uri
that can be opened by a ContentResolver
.
For example, if you want to open a document selected by the SAF picker, use the following code:
val inputStream = context.contentResolver.openInputStream(documentUri)
InputStream inputStream = context.getContentResolver().openInputStream(documentUri);
See the SAF picker in action on our free PDF Viewer for Android app.
Security
Android manages external storage using FUSE, a Unix-like daemon that can be seen as a virtual file system that prevents malicious users from accessing protected code. The actual FUSE implementation is written in C++ and can be executed only as root.
Final thoughts and reference
Android permissions must be treated wisely, and here at Nutrient, we focus heavily on security and privacy. The use of dangerous and special permissions should be avoided when not strictly required, and an app should work even when a permission is not granted. There are many tutorials on the internet about the Android permission model, but because it evolves rapidly, it’s always a good idea to check if the information is up to date, as in the latest Android API, many methods have changes.
Other useful resources
-
The great Nick Butcher talking about permissions in Android Marshmallow 6.0
-
“Mother, May I?” Asking for Permissions (Android Dev Summit 2015)
-
Forget the Storage Permission: Alternatives for sharing and collaborating by Ian Lake
FAQ
Here are a few frequently asked questions about storage permissions on Android.
What permissions are considered “dangerous” on Android?
Dangerous permissions are those that could impact user privacy, such as accessing contacts or external storage.
How does Android FUSE contribute to file security?
FUSE acts as a virtual file system that prevents unauthorized access, enhancing data protection on Android devices.
When does an Android app need storage permissions?
Apps need storage permissions when they require full access to the external storage, especially to read or write files directly.
Can apps access external storage without permissions?
Yes, apps can use the Storage Access Framework (SAF) picker to access files selectively without needing full storage permissions.
What happens if an app requests a denied permission?
If a permission is denied, the app should have a fallback to prevent errors or crashes and provide a smooth user experience.