Skip to content

Instantly share code, notes, and snippets.

@AlaaEddinAlbarghoth
Created December 1, 2022 16:35
Show Gist options
  • Select an option

  • Save AlaaEddinAlbarghoth/f3a4ca22fceb7f06c59feff7d0852cbc to your computer and use it in GitHub Desktop.

Select an option

Save AlaaEddinAlbarghoth/f3a4ca22fceb7f06c59feff7d0852cbc to your computer and use it in GitHub Desktop.
FileUtils for Android, using Kotlin
@file:Suppress("TooManyFunctions", "ComplexMethod", "ComplexMethod", "ComplexMethod")
package com.tawrid.helper.coroutines.extensions
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.database.DatabaseUtils
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import android.webkit.MimeTypeMap
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
object FileUtils {
private const val DOCUMENTS_DIR = "documents"
private const val AUTHORITY = "YOUR_AUTHORITY.provider"
/**
* TAG for log messages.
*/
private const val TAG = "FileUtils"
private const val DEBUG = false // Set to true to enable logging
/**
* Gets the extension of a file name, like ".png" or ".jpg".
*
* @param uri
* @return Extension including the dot("."); "" if there is no extension;
* null if uri was null.
*/
private fun getExtension(uri: String?): String? {
if (uri == null) {
return null
}
val dot = uri.lastIndexOf(".")
return if (dot >= 0) {
uri.substring(dot)
} else {
// No extension.
""
}
}
/**
* @return Whether the URI is a local one.
*/
private fun isLocal(url: String?): Boolean {
return url != null && !url.startsWith("http://") && !url.startsWith("https://")
}
/**
* @return True if Uri is a MediaStore Uri.
* @author paulburke
*/
fun isMediaUri(uri: Uri): Boolean {
return "media".equals(uri.authority, ignoreCase = true)
}
/**
* Convert File into Uri.
*
* @param file
* @return uri
*/
fun getUri(file: File?): Uri? {
return if (file != null) Uri.fromFile(file) else null
}
/**
* Returns the path only (without file name).
*
* @param file
* @return
*/
fun getPathWithoutFilename(file: File?): File? {
return if (file != null) {
if (file.isDirectory) {
// no file to be split off. Return everything
file
} else {
val filename = file.name
val filepath = file.absolutePath
// Construct path without file name.
var pathwithoutname = filepath.substring(
0,
filepath.length - filename.length
)
if (pathwithoutname.endsWith("/")) {
pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length - 1)
}
File(pathwithoutname)
}
} else null
}
/**
* @return The MIME type for the given file.
*/
private fun getMimeType(file: File): String? {
val extension = getExtension(file.name)
return if (extension!!.length > 0) MimeTypeMap.getSingleton().getMimeTypeFromExtension(
extension.substring(1)
) else "application/octet-stream"
}
/**
* @return The MIME type for the give Uri.
*/
fun getMimeType(context: Context, uri: Uri): String? {
val file = getPath(context, uri)?.let { File(it) }
return file?.let { getMimeType(it) }
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is local.
*/
private fun isLocalStorageDocument(uri: Uri): Boolean {
return AUTHORITY == uri.authority
}
/**
* @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
}
/**
* 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 = MediaStore.Files.FileColumns.DATA
val projection = arrayOf(
column
)
try {
cursor = context.contentResolver.query(
uri!!, projection, selection, selectionArgs,
null
)
if (cursor != null && cursor.moveToFirst()) {
if (DEBUG) DatabaseUtils.dumpCursor(cursor)
val column_index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
} finally {
cursor?.close()
}
return null
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.<br></br>
* <br></br>
* Callers should check whether the path is local before assuming it
* represents a local file.
*
* @param context The context.
* @param uri The Uri to query.
* @see .isLocal
* @see .getFile
*/
private fun getPath(context: Context, uri: Uri): String? {
if (DEBUG) Log.d(
"$TAG File -",
"Authority: " + uri.authority +
", Fragment: " + uri.fragment +
", Port: " + uri.port +
", Query: " + uri.query +
", Scheme: " + uri.scheme +
", Host: " + uri.host +
", Segments: " + uri.pathSegments.toString()
)
// DocumentProvider
if (DocumentsContract.isDocumentUri(context, uri)) {
// LocalStorageProvider
if (isLocalStorageDocument(uri)) {
// The path is the id
return DocumentsContract.getDocumentId(uri)
} else if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
if (id != null && id.startsWith("raw:")) {
return id.substring(4)
}
val contentUriPrefixesToTry = arrayOf(
"content://downloads/public_downloads",
"content://downloads/my_downloads"
)
for (contentUriPrefix in contentUriPrefixesToTry) {
val contentUri = ContentUris.withAppendedId(
Uri.parse(contentUriPrefix),
java.lang.Long.valueOf(id)
)
try {
val path = getDataColumn(context, contentUri, null, null)
if (path != null) {
return path
}
} catch (_: Exception) {
}
}
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
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[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(
split[1]
)
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
// Return the remote address
return if (isGooglePhotosUri(uri)) {
uri.lastPathSegment
} else getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
/**
* Convert Uri into File, if possible.
*
* @return file A local file that the Uri was pointing to, or null if the
* Uri is unsupported or pointed to a remote resource.
* @author paulburke
* @see .getPath
*/
fun getFile(context: Context, uri: Uri?): File? {
if (uri != null) {
val path = getPath(context, uri)
if (path != null && isLocal(path)) {
return File(path)
}
}
return null
}
fun getDocumentCacheDir(context: Context): File {
val dir = File(context.cacheDir, DOCUMENTS_DIR)
if (!dir.exists()) {
dir.mkdirs()
}
logDir(context.cacheDir)
logDir(dir)
return dir
}
private fun logDir(dir: File) {
if (!DEBUG) return
Log.d(TAG, "Dir=$dir")
dir.listFiles()?.let { files ->
for (file in files) {
Log.d(TAG, "File=" + file.path)
}
}
}
private fun generateFileName(name: String?, directory: File): File? {
name ?: return null
var file = File(directory, name)
if (file.exists()) {
var fileName = 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++
file = File(directory, "$fileName($index)$extension")
}
}
try {
if (!file.createNewFile()) {
return null
}
} catch (e: IOException) {
Log.w(TAG, e)
return null
}
logDir(directory)
return file
}
private fun saveFileFromUri(context: Context, uri: Uri, destinationPath: String?) {
var `is`: InputStream? = null
var bos: BufferedOutputStream? = null
try {
`is` = context.contentResolver.openInputStream(uri)
bos = BufferedOutputStream(FileOutputStream(destinationPath, false))
val buf = ByteArray(1024)
`is`!!.read(buf)
do {
bos.write(buf)
} while (`is`.read(buf) != -1)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
`is`?.close()
bos?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun getFileName(context: Context, uri: Uri): String? {
val mimeType = context.contentResolver.getType(uri)
var filename: String? = null
if (mimeType == 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)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment