Demystifying internal vs external storage in modern Android
Updated 6 Feb 2023
Android devs have the option of internal vs external storage when saving files. Whilst there’s a historical reason for this, the difference nowadays is a bit confusing.
Let’s go on a journey to understand why these options exist, which option you should go for, and what the better alternatives are.
Android used to be simple, but crazy insecure
In the old days, the internal and external choices had distinct purposes:
- Internal storage meant the device’s internal memory. Files stored here were locked to your app, meaning that other apps couldn’t access them regardless of their permissions.
- External storage meant anything that wasn’t the device’s internal memory, such as an inserted SD card. This was used as a free-for-all location that any app could access, so long as that app had the right permissions.
Whilst this was clear, it had a particular issue: that devices of the time had very little internal storage and relied on users plugging in SD cards. SD cards were by definition external storage.
So if you wanted to store a large amount of data — let’s say you were writing a photos app — you only really had the choice of external storage. It was therefore really common for apps to request permission to access external storage (via the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions).
But this was enormously insecure. Those external storage permissions would allow the app to read/write files anywhere on the SD card. So all your photos were made available to any app wanting to write its own data to external storage. And since external storage permissions were so commonly requested by apps, users would grant them without any real consideration. Cue malware apps stealing your data without your knowledge.
Android KitKat: Relaxed permissions made security stronger (!!)
Question: How can you solve a security problem by relaxing the permissions needed to do something?
Answer: Human psychology. When relaxing permissions needed to do non-dangerous stuff, you make people sit up and take notice when permission is requested.
This was what Android 4.4 (KitKat) did. For the first time it allowed apps to access their own unique places in external storage, without permission. Now when an app calls getExternalFilesDir() it gets its own directory created, which it can read from and write to freely.
The READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions are still needed to read/write anywhere else on external storage, but the point was that most apps shouldn’t ever need to do that.
And so the previously ubiquitous storage permissions dialog could start to disappear from everyday Android life. In time, this meant that users were much more wary whenever they did see it. And rightly so — now there was no longer an innocent reason to request it.
Emulated external storage: When internal storage becomes external
Why did Android phones stop offering SD card slots? Optimists would say it was to improve security, since SD cards could easily be removed from the phone and read elsewhere. Cynics would say that phone manufacturers benefitted from preventing consumers adding their own cheap storage, as they could upsell internal storage space at a high profit. Both points are of course true.
And phones did gradually stop offering external storage. This caused a problem with older apps which expected only external storage to be large enough for their needs, and blindly used getExternalFilesDir().
The Android team solved this problem with Emulated External Storage, which has been in the OS since Android 3. When an app requests external storage it actually gets a slice of the internal storage instead, and is none the wiser. This slice of internal storage has all the insecure rules of external storage applied to it.
Since API 21, apps can use Environment.isExternalStorageEmulated() to determine whether the device has physical or emulated external storage. For the vast majority of modern Android devices it will be emulated.
Adoptable external storage: When external storage becomes internal
Android 6 added the ability for a phone or tablet to format and encrypt an external storage device and adopt it — getting it to behave like internal storage. Adopted storage is tied by encryption to the device that created it. The encryption key lives on the internal storage, and so adopted storage is essentially as secure as internal.
Good points: This allows low-spec devices to increase their internal storage in a secure way.
Bad points: An adopted SD card, say, is inextricably linked to that phone. Remove it and your phone goes weird: you’ve essentially removed a random chunk of its apps and data. (Random, because the OS rather than the user chooses whether something is installed or saved to internal or adopted storage.) I can’t imagine any OS being able to handle that very well.
Android 10’s Scoped Storage completely changed external storage
Android 10 introduced Scoped Storage for external storage. In essence, this made the security of external storage very similar to internal storage. Apps would get their own directory which they could access (read/write) without permission, and no other app could do so (except see below).
The problem with Scoped Storage was that it left out some important use cases that previously required free-for-all access to shared storage. For example, what about getting access to a user’s photo gallery? These use cases were addressed on a case-by-case basis. They are as follows:
Allowing a user to select from a gallery of photos
Use Photo Picker, no permissions needed. (See my article on using Photo Picker with Kotlin + Compose).
Note that this is only available on Android 11+.
Writing photos to the user’s library
Use the MediaStore APIs. No permissions are needed to write new photos.
Reading photos from the user’s library
Also the MediaStore APIs. No permissions are needed if you’re only accessing files created by your own app. If you want access to the whole library, you need the following permissions:
<!-- Required only if your app needs to access images or photos
that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Required only if your app needs to access videos
that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Required only if your app needs to access audio files
that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- If your app doesn't need to access media files that other apps created,
set the "maxSdkVersion" attribute to "28" instead. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
Creating, reading and writing shared documents
For example if you want to save a PDF file to a location where other files can access it, or read geo-tracking data that has been made available by another app.
For this, you can use the Documents Provider. No permissions are needed, since the user is forced to be involved in the file selection process anyway.
Accessing large datasets cached by another app
In the situation where you’re working with big, shared, data sets which need to be downloaded, you can avoid re-downloading it if another app has already done so.
This can be achieved using the BlobStoreManager.
Full access to everything in external storage
There is still a permission that allows users to access everything in external storage, but it’s harder to reach now. It is appropriate only in very limited cases, for example file manager apps, backup systems, or virus scanners. The Play Store will not allow submissions of apps using this permission without a suitable explanation.
The permission is MANAGE_EXTERNAL_STORAGE
. Not only do apps have to declare that permission, they also have to direct users to manually enable it. Fire off a ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
intent to do so, which takes the user here:
Opting out of Scoped Storage
Whilst Scoped Storage was introduced in Android 10, it was such a significant change that it wasn’t enforced until Android 11. Even then, apps targeting API 29 could opt out of it by placing <application android:requestLegacyExternalStorage="true" />
in the manifest file. Note that apps targeting API 29 have not been accepted in the Play Store for years now, so this is essentially no longer supported.
So, that brings us up to date. The current version of Android hasn’t significantly altered storage permissions since this massive change in Android 10.
In conclusion: if you’re developing a modern app, for most cases use internal storage. But if you’re in one of the specific use cases above (photos, documents or large cached data sets) then use the relevant APIs instead.
Tom Colvin is CTO of Conseal Security, the mobile app security testing experts; and Apptaura, the app development specialists. Get in touch if I can help with any mobile security or development projects!