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

Android 10’s & 11’s Scoped Storage : Image Picker

What is Scoped Storage?

In Android 10 and 11(API level 29/30) , Google introduced the concept of Scoped Storage, which enhances the platform, giving better protection to app and user data on external storage. Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. After updating in Android 11 (API level 30) ,the system ignores, the requestLegacyExternalStorage flag.

Getting Started

  • If you target Android 10 or higher, set the value of requestLegacyExternalStorage to true in your app's manifest file.
  • If your app target Android 10 or higher , set the value of requestLegacyExternalStorage to true in your app's manifest file for capturing image from camera:

AndroidManifest

<manifest
......
<uses-permission
android:name=
"android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name=
"android.permission.WRITE_EXTERNAL_STORAGE" />
<application
<!-- This attribute is "false" by default on apps targeting Android 10 or Higher -->

android:requestLegacyExternalStorage="true"
</application>
</manifest>

Now , I’m going to explain scoped storage using example of capture image from Gallery :

  1. Pick image : Download Folder:
if (isDownloadsDocument(uri)) 
{// DownloadsProvider
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, "r", null)
parcelFileDescriptor?.let
{
val inputStream = FileInputStream(
parcelFileDescriptor.fileDescriptor)
val file = File(
context.cacheDir,
context.contentResolver.getFileName(uri))
val outputStream = FileOutputStream(file)
IOUtils.copyStream(inputStream, outputStream)
return file.path
}
}

getFileName(uri)

fun ContentResolver.getFileName(fileUri: Uri): String 
{
var name = ""
val returnCursor = this.query(fileUri, null, null, null, null)
if (returnCursor != null)
{
val nameIndex = returnCursor.getColumnIndex(
OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
name = returnCursor.getString(nameIndex)
returnCursor.close()
}
return URLEncoder.encode(name, "utf-8")
}

2. Pick image : Google Drive :

if (isGoogleDriveUri(uri)) 
{
return getDriveFilePath(context, uri)
}

getDriveFilePath(context, uri)

fun getDriveFilePath(context: Context, uri: Uri): String 
{
val returnUri = uri
val returnCursor: Cursor? = context.contentResolver.query(returnUri, null, null, null, null)
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
val nameIndex: Int = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex: Int = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name: String = (returnCursor.getString(nameIndex))
val size = java.lang.Long.toString(returnCursor.getLong(sizeIndex))
val file = File(context.cacheDir,URLEncoder.encode(name,"utf-8"))
try
{
val inputStream:InputStream? = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(file)
val read: Int = 0
val maxBufferSize: Int = 1 * 1024 * 1024
val bytesAvailable: Int = inputStream!!.available()
//int bufferSize = 1024;
val bufferSize: Int = Math.min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
inputStream.use
{
inputStream: InputStream ->
outputStream.use{ fileOut ->
while (true)
{
val length = inputStream.read(buffers)
if (length <= 0)
break
fileOut.write(buffers, 0, length)
}
fileOut.flush()
fileOut.close()
}
}

Log.e("File Size", "Size " + file.length())
inputStream.close()

Log.e("File Path", "Path " + file.path)
Log.e("File Size", "Size " + file.length())
} catch (e: Exception)
{
Log.e("Exception", e.message.toString())
}
return file.path
}

3. Pick image : Recent Folder/MediaDocument :

if (isMediaDocument(uri)) 
{
val docId: String = DocumentsContract.getDocumentId(uri)
val split: List<String> = docId.split(":")
val type: String = split[0]
var contentUri: Uri? = null
val selection: String = "_id=?"
val selectionArgs = arrayOf(split[1])
return copyFileToInternalStorage(
context, uri, context.getString(R.string.app_name))


}

copyFileInternalStorage(context,uri,”test”)

private fun copyFileToInternalStorage(mContext: Context, uri: Uri, newDirName: String): String 
{
val returnCursor: Cursor? = mContext.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE), null, null, null)
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name = returnCursor.getString(nameIndex)
val size = java.lang.Long.toString(returnCursor.getLong(sizeIndex))
val output: File
if (newDirName != "")
{
val dir = File(mContext.filesDir.toString()+ "/" + newDirName)
if (!dir.exists())
{
dir.mkdir()
}
output = File(mContext.filesDir.toString()
+ "/" + newDirName
+ "/" + URLEncoder.encode(name, "utf-8"))
} else
{
output = File(mContext.filesDir.toString()
+ "/" + URLEncoder.encode(name, "utf-8"))
}
try {
val inputStream: InputStream? =
mContext.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(output)
var read = 0
val bufferSize = 1024
val buffers = ByteArray(bufferSize)
while (inputStream!!.read(buffers).also { read = it }
!= -1)
{
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
} catch (e: java.lang.Exception) {
Log.e("Exception", e.message!!)
}
return output.path
}

4. Pick image : MediaStore (and general) :

if ("content".equals(uri.scheme, ignoreCase = true)) 
{
return
if (isGooglePhotosUri(uri))
uri.lastPathSegment!!
else copyFileToInternalStorage(
context,uri,
context.getString(R.string.app_name))
}

5. Pick image : External Storage :

if (isExternalStorageDocument(uri)) 
{// ExternalStorageProvider
val docId: String = DocumentsContract.getDocumentId(uri)
val split: List<String> = docId.split(":")
val type: String = split[0]

// This is for checking Main Memory
if ("primary".equals(type, ignoreCase = true))
{
if (split.size > 1)
{
return context.getExternalFilesDir(null).toString() + "/" + split[1]
}
else

{
return context.getExternalFilesDir(null).toString() + "/"
}
// This is for checking SD Card
} else {
return "storage" + "/" + docId.replace(":", "/")
}
}

Here is all provider for storage:

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean
{
return "com.android.externalstorage.documents"
.equals(uri.authority)
}

fun isGoogleDriveUri(uri: Uri): Boolean
{
return "com.google.android.apps.docs.storage".
equals(uri.authority) ||
"com.google.android.apps.docs.storage.legacy".
equals(uri.authority)
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean
{
return "com.android.providers.downloads.documents"
.equals(uri.authority)
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean
{
return "com.android.providers.media.documents"
.equals(uri.authority)
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean
{
return "com.google.android.apps.photos.content"
.equals(uri.authority)
}

Here is the final code for pickup image from any location:

getRealPathFromURI(context,uri)

@SuppressLint("NewApi")
fun getRealPathFromURI(context: Context, uri: Uri): String
{
val isKitKat: Boolean =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT

if (isKitKat && DocumentsContract.isDocumentUri(context, uri))
{ //main if start

// DocumentProvider
if (isGoogleDriveUri(uri))
{
return getDriveFilePath(context, uri)
}

else if (isExternalStorageDocument(uri))
{// ExternalStorageProvider

val docId: String = DocumentsContract.getDocumentId(uri)
val split: List<String> = docId.split(":")
val type: String = split[0]
// This is for checking Main Memory
if ("primary".equals(type, ignoreCase = true))
{
if (split.size > 1)
{
return context.getExternalFilesDir(null).toString()
+ "/" + split[1]
}
else
{
return context.getExternalFilesDir(null).
toString() + "/"
}
// This is for checking SD Card
} else
{
return "storage" + "/" + docId.replace(":", "/")
}
} else if (isDownloadsDocument(uri))
{
// DownloadsProvider
val parcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, "r", null)
parcelFileDescriptor?.let
{
val
inputStream = FileInputStream(
parcelFileDescriptor.fileDescriptor)
val file = File(
context.cacheDir,
context.contentResolver.getFileName(uri))
val outputStream = FileOutputStream(file)
IOUtils.copyStream(inputStream, outputStream)
return file.path
}
}

else if (isMediaDocument(uri))
{
val docId: String =DocumentsContract.getDocumentId(uri)
val split: List<String> = docId.split(":")
val type: String = split[0]
return copyFileToInternalStorage(context,uri,"app name")

}
}//main if end
else if
("content".equals(uri.scheme, ignoreCase = true))
{
// MediaStore (and general)
return if (isGooglePhotosUri(uri)) uri.lastPathSegment!!
else copyFileToInternalStorage(
context,
uri,
"your app name")
}
else if ("file".equals(uri.scheme, ignoreCase = true)) {
// File
return uri.path!!
}
return null!!
}

fun ContentResolver.getFileName(fileUri: Uri): String
{
var name = ""
val returnCursor = this.query(fileUri, null, null, null, null)
if (returnCursor != null) {
val nameIndex =returnCursor.getColumnIndex(
OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
name = returnCursor.getString(nameIndex)
returnCursor.close()
}
return URLEncoder.encode(name, "utf-8")
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents".
equals(uri.authority)
}

fun isGoogleDriveUri(uri: Uri): Boolean {
return "com.google.android.apps.docs.storage".
equals(uri.authority)
|| "com.google.android.apps.docs.storage.legacy".
equals(uri.authority)
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents"
.equals(uri.authority)
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents"
.equals(uri.authority)
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content"
.equals(uri.authority)
}


fun getDriveFilePath(context: Context, uri: Uri): String
{
val returnUri = uri
val returnCursor: Cursor? = context.contentResolver.query
(returnUri, null, null, null, null)

val nameIndex: Int = returnCursor!!.getColumnIndex
(OpenableColumns.DISPLAY_NAME)
val sizeIndex: Int = returnCursor.getColumnIndex
(OpenableColumns.SIZE)

returnCursor.moveToFirst()
val name: String = (returnCursor.getString(nameIndex))
val size = Long.toString(returnCursor.getLong(sizeIndex))
val file = File(context.cacheDir,
URLEncoder.encode(name, "utf-8"))
try {
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(file)
val read: Int = 0
val maxBufferSize: Int = 1 * 1024 * 1024
val bytesAvailable: Int = inputStream!!.available()
//int bufferSize = 1024;
val bufferSize: Int =
Math.min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
inputStream.use { inputStream: InputStream ->
outputStream.use { fileOut ->
while
(true) {
val length = inputStream.read(buffers)
if (length <= 0)
break
fileOut.write(buffers, 0, length)
}
fileOut.flush()
fileOut.close()
}
}

Log.e("File Size", "Size " + file.length())
inputStream.close()
Log.e("File Path", "Path " + file.path)
Log.e("File Size", "Size " + file.length())
} catch (e: Exception) {
Log.e("Exception", e.message.toString())
}
return file.path
}

private fun copyFileToInternalStorage(mContext: Context, uri: Uri, newDirName: String): String
{
val returnCursor: Cursor? = mContext.contentResolver.query(
uri,
arrayOf(OpenableColumns.DISPLAY_NAME,
OpenableColumns.SIZE),
null,
null,
null)

val nameIndex = returnCursor!!.getColumnIndex
(OpenableColumns.DISPLAY_NAME)
val sizeIndex = returnCursor.getColumnIndex
(OpenableColumns.SIZE)
returnCursor.moveToFirst() val name = returnCursor.getString(nameIndex)
val size = java.lang.Long.toString
(returnCursor.getLong(sizeIndex))
val output: File
if (newDirName != "")
{
val dir =File(mContext.filesDir.toString()
+ "/" + newDirName)
if (!dir.exists())
{
dir.mkdir()
}
output = File(
mContext.filesDir.toString()
+ "/" + newDirName + "/"
+ URLEncoder.encode(name,"utf-8"))
}
else
{
output = File(mContext.filesDir.toString()
+ "/" + URLEncoder.encode(name, "utf-8"))
}
try
{
val inputStream: InputStream?=
mContext.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(output)
var read = 0
val bufferSize = 1024
val buffers = ByteArray(bufferSize)
while (inputStream!!.read(buffers).also { read = it} != -1){
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
} catch (e: java.lang.Exception) {
Log.e("Exception", e.message!!)
}
return output.path
}
}//function finish

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

Thanks . Happy Coding!

Android Developer | Tech Enthusiast | Mobile Technology Expert at https://www.emedhealthtech.com