Android 10’s Scoped Storage: Image Picker (Gallery/Camera)

Shashank Pednekar
5 min readNov 10, 2019

--

To give users more control over their files and to limit file clutter, apps targeting Android 10 (API level 29) and higher are given scoped access into an external storage device, or scoped storage, by default. Such apps can see only their app-specific directory accessed using Context.getExternalFilesDir() and specific types of media. It's a best practice to use scoped storage unless your app needs access to a file that doesn't reside in either the app-specific directory or the MediaStore.

Why Scoped Storage

First and foremost, it stops malicious apps that depend on the user for granting access to their sensitive data because they did not read what they saw in the dialog and just clicked on allow. It also allows a developer to have their own space on the storage of your device that is private without asking for any specific permissions and no other app can access any document it creates without user granting temporary permissions.

Another reason to do this is because when an application having storage permissions, creates files or folders they get scattered all over user’s storage directory making it cluttered, and if the user decides to uninstall the app the files / folders that were created by the app still exist in that same location, unless you manually delete those files / folders once you’re done using them.

Can I opt out of Scoped Storage?

Yes, you can opt out temporarily by using following methods:

  • Target Android 9 (API level 28) or lower.
  • If you target Android 10 or higher, set the value of requestLegacyExternalStorage to true in your app's manifest file

As per Google Play requirements, to provide users with the best Android experience possible, the Google Play Console will continue to require that apps target a recent API level. Apps will be required to use scoped storage in next year’s major platform release for all apps, independent of target SDK level. Therefore, you should ensure that your app works with scoped storage well in advance.

So, what’s the solution?

Talking about solution, it’s not exactly new, but it is now strictly enforced and no longer optional. With Android 4.4 (Api 19) introduces the Storage Access Framework (SAF). This gave apps a way to access files in other folders using an Android API instead of using standard programming file operations.

Following types of files within an external storage device without needing to request any storage-related user permissions:

I’m going to explain scoped storage changes using example of Image picker(Gallery/Camera) with compression. You can find source code here.

Creating file directory URI using FileProvider

To capture image we have to create external file directory using getExternalFilesDir(Environment.DIRECTORY_DCIM) and create uri from it.

There are other Environment variables also available like Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_PODCASTS, Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PICTURES, or Environment.DIRECTORY_MOVIES. This type value can be null for the root of the files directory. Use as per your requirements.

But passing file:// URIs outside the package domain may leave the receiver with an inaccessible path. Therefore, attempts to pass a file:// URI trigger a FileUriExposedException. The recommended way to share the content of a private file is using the FileProvider.

Following steps for create FileProvider:

  1. Android Manifest

2. Specifying available files

3. Generating content URI for a file

Take a photo or Select Image from gallery

Here we’re using Intent.createChooser()to take a photo using camera and select image from gallery.

Read Bitmap from URI

In onActivityResult() intentData parameter contains URI if user selects image from gallery, otherwise data will be available in imageUri which we were created earlier.

Note: You should not do this operation on UI thread. Do it in the background thread. For this I used Coroutines, its simple and easy to use.

To start image processing, we need to get bitmap from uri/path. Earlier we were using BitmapFactory.decodeFile(path), but it return null for external files. So, we have to use following method to get bitmap by using SAF method.

Use URI outside Activity Scope

Once the user select image from gallery, your app can return its content URI in onActivityResult(). You have access to the content of URI from the Activity that receives the URI. You can pass this access to other components of your app or even components of other apps by setting Intent flag as follows:

val intent = Intent(this, AnotherActivity::class.java)
.setData(uri)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)

startActivity(intent)

Persist URI Permission

URI permission lasts until the user’s device restarts. To prevent this you can use contentResolver.takePersistableUriPermission()as follows:

val takeFlags: Int = intent.flags and
(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, takeFlags)

Manage Package visibility on Android 11

Add the following code in the manifest file:

<manifest >
...
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.PICK" />
<data android:mimeType="image/*" />
</intent>
</queries>
...
</manifest>

Following source code for a complete example of image picker with compression:

References:

https://developer.android.com/training/data-storage/files/external-scopedhttps://developer.android.com/guide/topics/providers/document-providerhttps://www.androidcentral.com/what-scoped-storage-android-q

If there are any questions or feedback regarding this post then please do reach out.

Happy Coding!

--

--

Shashank Pednekar
Shashank Pednekar

Written by Shashank Pednekar

Android Developer | Mumbai, India

Responses (6)