Skip to content

Instantly share code, notes, and snippets.

@ChristopherME
Created January 29, 2020 19:42
Show Gist options
  • Save ChristopherME/23b9a7adec043d4efec3af63fbe46211 to your computer and use it in GitHub Desktop.
Save ChristopherME/23b9a7adec043d4efec3af63fbe46211 to your computer and use it in GitHub Desktop.
File Selector
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import java.io.*
import java.lang.Exception
import android.provider.OpenableColumns
import android.support.annotation.NonNull
import android.support.annotation.Nullable
object FileSelector {
private val TAG = this.javaClass.simpleName
fun getPath(context: Context, uri: Uri) : String? {
// Check here to LOLLIPOP or new version
val isLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
val isOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
// Document Provider
if (isLollipop && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorage Provider
if (isExternalStorageDocument(uri)) {
val documentID = DocumentsContract.getDocumentId(uri)
val splitted = documentID.split(":")
val type = splitted[0]
if ("primary" == type) {
return Environment.getExternalStorageState() + "/" + splitted[1]
}
}
// Downloads Provider
else if (isDownloadsDocument(uri)) {
val documentID = DocumentsContract.getDocumentId(uri)
//First fix
if (documentID.startsWith("raw:/")){
return documentID.replace("raw:","")
}
// search in these folders
val contentUriPrefixesToTry = arrayOf("content://downloads/public_downloads", "content://downloads/my_downloads", "content://downloads/all_downloads")
for (contentUriPrefix in contentUriPrefixesToTry) {
try {
val contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), documentID.toLong())
val path = getDataColumn(context, contentUri, null, null)
if (path != null) {
return path
}
} catch (e: Exception){
e.printStackTrace()
Log.e(TAG, "content uri exception: ${e.message}")
}
}
// path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams
val fileName = getFileName(context, uri)
val cacheDir = getDocumentCacheDir(context)
val file = generateFileName(fileName, cacheDir)
var destinationPath: String? = null
if (file != null) {
destinationPath = file.absolutePath
saveFileFromUri(context, uri, destinationPath)
}
return destinationPath
//region Old way
//val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), documentID.toLong())
//return getDataColumn(context, contentUri, null, null)
//endregion
}
// Media Provider
else if (isMediaDocument(uri)) {
val documentID = DocumentsContract.getDocumentId(uri)
val splitted = documentID.split(":")
val type = splitted[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(splitted[1])
return getDataColumn(context, contentUri!!, selection, selectionArgs)
}
}
// MediaStore and general
else if ("content" == uri.scheme) {
// Return the remote address
if (isGooglePhotosUri(uri)) {
return uri.lastPathSegment
}
return getDataColumn(context, uri, null, null)
}
// File
else if ("file" == uri.scheme) {
return uri.path
}
return null
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context
* The context.
* @param uri
* The Uri to query.
* @param selection
* (Optional) Filter used in the query.
* @param selectionArgs
* (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = context.contentResolver.query(uri, projection,
selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(index)
}
} finally {
cursor?.close()
}
return null
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri
.authority
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri
.authority
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri
.authority
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
private fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri
.authority
}
// New functions for create temporal file in cache memory
private fun getFileName(@NonNull context: Context, uri: Uri): String? {
val mimeType = context.contentResolver.getType(uri)
var filename: String? = null
if (mimeType == null && context != null) {
val path = getPath(context, uri)
filename = if (path == null) {
getName(uri.toString())
} else {
val file = File(path)
file.name
}
} else {
val returnCursor = context.contentResolver.query(uri, null, null, null, null)
if (returnCursor != null) {
val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
filename = returnCursor.getString(nameIndex)
returnCursor.close()
}
}
return filename
}
private fun getName(filename: String?): String? {
if (filename == null) {
return null
}
val index = filename.lastIndexOf('/')
return filename.substring(index + 1)
}
private fun getDocumentCacheDir(context: Context): File {
val dir = File(context.cacheDir, "documents")
if (!dir.exists()) {
dir.mkdirs()
}
logDir(context.cacheDir)
logDir(dir)
return dir
}
private fun logDir(dir: File) {
Log.d(TAG, "Dir=$dir")
val files = dir.listFiles()
for (file in files) {
Log.d(TAG, "File=" + file.path)
}
}
@Nullable
fun generateFileName(@Nullable fileName: String?, directory: File): File? {
var name: String? = fileName
if (name != null) {
var file = File(directory, name)
if (file.exists()) {
var fileName: String = name
var extension = ""
val dotIndex = name.lastIndexOf('.')
if (dotIndex > 0) {
fileName = name.substring(0, dotIndex)
extension = name.substring(dotIndex)
}
var index = 0
while (file.exists()) {
index++
name = "$fileName($index)$extension"
file = File(directory, name)
}
}
try {
if (!file.createNewFile()) {
return null
}
} catch (e: IOException) {
Log.w(TAG, e)
return null
}
logDir(directory)
return file
}
return null
}
private fun saveFileFromUri(context: Context, uri: Uri, destinationPath: String) {
var inputStream: InputStream? = null
var bos: BufferedOutputStream? = null
try {
inputStream = context.contentResolver.openInputStream(uri)
bos = BufferedOutputStream(FileOutputStream(destinationPath, false))
val buf = ByteArray(1024)
inputStream!!.read(buf)
do {
bos.write(buf)
} while (inputStream.read(buf) != -1)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
inputStream?.close()
bos?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment