2018-09-12 16:13:05 +00:00
package com.mr.flutter.plugin.filepicker ;
2018-12-27 14:02:24 +00:00
2020-06-04 21:49:39 +00:00
import android.annotation.SuppressLint ;
2018-09-12 16:13:05 +00:00
import android.annotation.TargetApi ;
import android.content.Context ;
import android.database.Cursor ;
import android.net.Uri ;
import android.os.Build ;
2021-07-28 21:35:11 +00:00
import android.os.Environment ;
2020-06-04 21:49:39 +00:00
import android.os.storage.StorageManager ;
2018-09-12 16:13:05 +00:00
import android.provider.DocumentsContract ;
2020-12-13 16:04:55 +00:00
import android.provider.OpenableColumns ;
2018-09-12 16:13:05 +00:00
import android.util.Log ;
2020-04-05 16:01:31 +00:00
import android.webkit.MimeTypeMap ;
2019-03-08 01:42:07 +00:00
2020-06-04 21:49:39 +00:00
import androidx.annotation.Nullable ;
2020-09-11 13:53:18 +00:00
import java.io.BufferedInputStream ;
2020-09-25 17:25:01 +00:00
import java.io.BufferedOutputStream ;
2020-05-01 16:49:35 +00:00
import java.io.File ;
2020-09-11 13:53:18 +00:00
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
2019-03-08 01:42:07 +00:00
import java.io.FileOutputStream ;
import java.io.IOException ;
import java.io.InputStream ;
2020-06-04 21:49:39 +00:00
import java.lang.reflect.Array ;
import java.lang.reflect.Method ;
2020-04-05 16:01:31 +00:00
import java.util.ArrayList ;
2019-09-17 09:15:05 +00:00
import java.util.Random ;
2019-03-08 01:42:07 +00:00
2018-12-27 14:02:24 +00:00
public class FileUtils {
2018-09-12 16:13:05 +00:00
2019-03-08 01:42:07 +00:00
private static final String TAG = " FilePickerUtils " ;
2020-06-04 21:49:39 +00:00
private static final String PRIMARY_VOLUME_NAME = " primary " ;
2018-09-12 16:13:05 +00:00
2020-04-05 16:01:31 +00:00
public static String [ ] getMimeTypes ( final ArrayList < String > allowedExtensions ) {
if ( allowedExtensions = = null | | allowedExtensions . isEmpty ( ) ) {
return null ;
}
final ArrayList < String > mimes = new ArrayList < > ( ) ;
for ( int i = 0 ; i < allowedExtensions . size ( ) ; i + + ) {
final String mime = MimeTypeMap . getSingleton ( ) . getMimeTypeFromExtension ( allowedExtensions . get ( i ) ) ;
if ( mime = = null ) {
Log . w ( TAG , " Custom file type " + allowedExtensions . get ( i ) + " is unsupported and will be ignored. " ) ;
continue ;
}
mimes . add ( mime ) ;
}
Log . d ( TAG , " Allowed file extensions mimes: " + mimes ) ;
return mimes . toArray ( new String [ 0 ] ) ;
}
2020-03-14 17:32:05 +00:00
public static String getFileName ( Uri uri , final Context context ) {
2018-12-27 14:02:24 +00:00
String result = null ;
2020-12-13 16:04:55 +00:00
try {
if ( uri . getScheme ( ) . equals ( " content " ) ) {
Cursor cursor = context . getContentResolver ( ) . query ( uri , new String [ ] { OpenableColumns . DISPLAY_NAME } , null , null , null ) ;
try {
if ( cursor ! = null & & cursor . moveToFirst ( ) ) {
result = cursor . getString ( cursor . getColumnIndex ( OpenableColumns . DISPLAY_NAME ) ) ;
2020-03-14 17:32:05 +00:00
}
2020-12-13 16:04:55 +00:00
} finally {
2020-04-05 16:01:31 +00:00
cursor . close ( ) ;
}
2018-12-27 14:02:24 +00:00
}
2020-12-13 16:04:55 +00:00
if ( result = = null ) {
result = uri . getPath ( ) ;
int cut = result . lastIndexOf ( '/' ) ;
if ( cut ! = - 1 ) {
result = result . substring ( cut + 1 ) ;
}
}
} catch ( Exception ex ) {
Log . e ( TAG , " Failed to handle file name: " + ex . toString ( ) ) ;
2018-12-27 14:02:24 +00:00
}
2020-12-13 16:04:55 +00:00
return result ;
2018-12-27 14:02:24 +00:00
}
2020-05-01 16:49:35 +00:00
public static boolean clearCache ( final Context context ) {
try {
final File cacheDir = new File ( context . getCacheDir ( ) + " /file_picker/ " ) ;
final File [ ] files = cacheDir . listFiles ( ) ;
if ( files ! = null ) {
for ( final File file : files ) {
file . delete ( ) ;
}
}
} catch ( final Exception ex ) {
Log . e ( TAG , " There was an error while clearing cached files: " + ex . toString ( ) ) ;
return false ;
}
return true ;
}
2021-06-22 15:21:05 +00:00
public static void loadData ( final File file , FileInfo . Builder fileInfo ) {
try {
2020-09-11 13:53:18 +00:00
int size = ( int ) file . length ( ) ;
byte [ ] bytes = new byte [ size ] ;
2019-03-08 01:42:07 +00:00
try {
2020-09-11 13:53:18 +00:00
BufferedInputStream buf = new BufferedInputStream ( new FileInputStream ( file ) ) ;
buf . read ( bytes , 0 , bytes . length ) ;
buf . close ( ) ;
} catch ( FileNotFoundException e ) {
Log . e ( TAG , " File not found: " + e . getMessage ( ) , null ) ;
} catch ( IOException e ) {
Log . e ( TAG , " Failed to close file streams: " + e . getMessage ( ) , null ) ;
}
fileInfo . withData ( bytes ) ;
2019-03-08 01:42:07 +00:00
2021-06-22 15:21:05 +00:00
} catch ( Exception e ) {
Log . e ( TAG , " Failed to load bytes into memory with error " + e . toString ( ) + " . Probably the file is too big to fit device memory. Bytes won't be added to the file this time. " ) ;
}
}
public static FileInfo openFileStream ( final Context context , final Uri uri , boolean withData ) {
Log . i ( TAG , " Caching from URI: " + uri . toString ( ) ) ;
FileOutputStream fos = null ;
final FileInfo . Builder fileInfo = new FileInfo . Builder ( ) ;
final String fileName = FileUtils . getFileName ( uri , context ) ;
final String path = context . getCacheDir ( ) . getAbsolutePath ( ) + " /file_picker/ " + ( fileName ! = null ? fileName : new Random ( ) . nextInt ( 100000 ) ) ;
final File file = new File ( path ) ;
if ( ! file . exists ( ) ) {
2020-09-11 13:53:18 +00:00
file . getParentFile ( ) . mkdirs ( ) ;
try {
fos = new FileOutputStream ( path ) ;
try {
2020-09-25 17:25:01 +00:00
final BufferedOutputStream out = new BufferedOutputStream ( fos ) ;
2020-09-11 13:53:18 +00:00
final InputStream in = context . getContentResolver ( ) . openInputStream ( uri ) ;
2019-03-08 01:42:07 +00:00
2020-09-11 13:53:18 +00:00
final byte [ ] buffer = new byte [ 8192 ] ;
int len = 0 ;
2020-03-14 17:32:05 +00:00
2020-09-11 13:53:18 +00:00
while ( ( len = in . read ( buffer ) ) > = 0 ) {
out . write ( buffer , 0 , len ) ;
}
out . flush ( ) ;
} finally {
fos . getFD ( ) . sync ( ) ;
}
} catch ( final Exception e ) {
try {
fos . close ( ) ;
} catch ( final IOException | NullPointerException ex ) {
Log . e ( TAG , " Failed to close file streams: " + e . getMessage ( ) , null ) ;
return null ;
}
Log . e ( TAG , " Failed to retrieve path: " + e . getMessage ( ) , null ) ;
2019-03-08 01:42:07 +00:00
return null ;
}
2020-03-14 17:32:05 +00:00
}
2019-03-08 01:42:07 +00:00
2020-09-09 23:52:45 +00:00
Log . d ( TAG , " File loaded and cached at: " + path ) ;
2021-06-22 15:21:05 +00:00
if ( withData ) {
loadData ( file , fileInfo ) ;
}
2020-09-09 23:52:45 +00:00
fileInfo
. withPath ( path )
. withName ( fileName )
2021-03-08 12:08:55 +00:00
. withSize ( Long . parseLong ( String . valueOf ( file . length ( ) ) ) ) ;
2020-09-09 23:52:45 +00:00
return fileInfo . build ( ) ;
2019-03-08 01:42:07 +00:00
}
2020-06-04 21:49:39 +00:00
@Nullable
2021-07-28 21:35:11 +00:00
@SuppressWarnings ( " deprecation " )
2020-09-11 13:53:18 +00:00
public static String getFullPathFromTreeUri ( @Nullable final Uri treeUri , Context con ) {
2020-09-09 23:52:45 +00:00
if ( treeUri = = null ) {
return null ;
}
2021-07-28 21:35:11 +00:00
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . R ) {
if ( isDownloadsDocument ( treeUri ) ) {
String docId = DocumentsContract . getDocumentId ( treeUri ) ;
String extPath = Environment . getExternalStoragePublicDirectory ( Environment . DIRECTORY_DOWNLOADS ) . getPath ( ) ;
if ( docId . equals ( " downloads " ) ) {
return extPath ;
} else if ( docId . matches ( " ^ms[df] \\ :.* " ) ) {
String fileName = getFileName ( treeUri , con ) ;
return extPath + " / " + fileName ;
} else if ( docId . startsWith ( " raw: " ) ) {
String rawPath = docId . split ( " : " ) [ 1 ] ;
return rawPath ;
}
return null ;
}
}
2020-06-04 21:49:39 +00:00
String volumePath = getVolumePath ( getVolumeIdFromTreeUri ( treeUri ) , con ) ;
2020-09-09 23:52:45 +00:00
FileInfo . Builder fileInfo = new FileInfo . Builder ( ) ;
if ( volumePath = = null ) {
2020-09-11 13:53:18 +00:00
return File . separator ;
2020-09-09 23:52:45 +00:00
}
2020-06-04 21:49:39 +00:00
if ( volumePath . endsWith ( File . separator ) )
volumePath = volumePath . substring ( 0 , volumePath . length ( ) - 1 ) ;
String documentPath = getDocumentPathFromTreeUri ( treeUri ) ;
2020-09-09 23:52:45 +00:00
2020-06-04 21:49:39 +00:00
if ( documentPath . endsWith ( File . separator ) )
documentPath = documentPath . substring ( 0 , documentPath . length ( ) - 1 ) ;
if ( documentPath . length ( ) > 0 ) {
2020-09-09 23:52:45 +00:00
if ( documentPath . startsWith ( File . separator ) ) {
2020-09-11 13:53:18 +00:00
return volumePath + documentPath ;
2020-09-09 23:52:45 +00:00
}
else {
2020-09-11 13:53:18 +00:00
return volumePath + File . separator + documentPath ;
2020-09-09 23:52:45 +00:00
}
} else {
2020-09-11 13:53:18 +00:00
return volumePath ;
2020-09-09 23:52:45 +00:00
}
2020-06-04 21:49:39 +00:00
}
2021-07-28 21:35:11 +00:00
@Nullable
private static String getDirectoryPath ( Class < ? > storageVolumeClazz , Object storageVolumeElement ) {
try {
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . R ) {
Method getPath = storageVolumeClazz . getMethod ( " getPath " ) ;
return ( String ) getPath . invoke ( storageVolumeElement ) ;
}
Method getDirectory = storageVolumeClazz . getMethod ( " getDirectory " ) ;
File f = ( File ) getDirectory . invoke ( storageVolumeElement ) ;
if ( f ! = null )
return f . getPath ( ) ;
} catch ( Exception ex ) {
return null ;
}
return null ;
}
2020-06-04 21:49:39 +00:00
@SuppressLint ( " ObsoleteSdkInt " )
private static String getVolumePath ( final String volumeId , Context context ) {
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . LOLLIPOP ) return null ;
try {
StorageManager mStorageManager =
( StorageManager ) context . getSystemService ( Context . STORAGE_SERVICE ) ;
Class < ? > storageVolumeClazz = Class . forName ( " android.os.storage.StorageVolume " ) ;
Method getVolumeList = mStorageManager . getClass ( ) . getMethod ( " getVolumeList " ) ;
Method getUuid = storageVolumeClazz . getMethod ( " getUuid " ) ;
Method isPrimary = storageVolumeClazz . getMethod ( " isPrimary " ) ;
Object result = getVolumeList . invoke ( mStorageManager ) ;
2021-07-28 21:35:11 +00:00
if ( result = = null )
return null ;
2020-06-04 21:49:39 +00:00
final int length = Array . getLength ( result ) ;
for ( int i = 0 ; i < length ; i + + ) {
Object storageVolumeElement = Array . get ( result , i ) ;
String uuid = ( String ) getUuid . invoke ( storageVolumeElement ) ;
Boolean primary = ( Boolean ) isPrimary . invoke ( storageVolumeElement ) ;
// primary volume?
2021-07-28 21:35:11 +00:00
if ( primary ! = null & & PRIMARY_VOLUME_NAME . equals ( volumeId ) ) {
return getDirectoryPath ( storageVolumeClazz , storageVolumeElement ) ;
}
2020-06-04 21:49:39 +00:00
// other volumes?
2021-07-28 21:35:11 +00:00
if ( uuid ! = null & & uuid . equals ( volumeId ) ) {
return getDirectoryPath ( storageVolumeClazz , storageVolumeElement ) ;
}
2020-06-04 21:49:39 +00:00
}
// not found.
return null ;
} catch ( Exception ex ) {
return null ;
}
}
2021-07-28 21:35:11 +00:00
private static boolean isDownloadsDocument ( Uri uri ) {
return " com.android.providers.downloads.documents " . equals ( uri . getAuthority ( ) ) ;
}
2020-06-04 21:49:39 +00:00
@TargetApi ( Build . VERSION_CODES . LOLLIPOP )
private static String getVolumeIdFromTreeUri ( final Uri treeUri ) {
final String docId = DocumentsContract . getTreeDocumentId ( treeUri ) ;
final String [ ] split = docId . split ( " : " ) ;
if ( split . length > 0 ) return split [ 0 ] ;
else return null ;
}
@TargetApi ( Build . VERSION_CODES . LOLLIPOP )
private static String getDocumentPathFromTreeUri ( final Uri treeUri ) {
final String docId = DocumentsContract . getTreeDocumentId ( treeUri ) ;
final String [ ] split = docId . split ( " : " ) ;
if ( ( split . length > = 2 ) & & ( split [ 1 ] ! = null ) ) return split [ 1 ] ;
else return File . separator ;
}
2018-09-12 16:13:05 +00:00
}