Last active
March 1, 2019 13:45
-
-
Save rok5ek/2e92883af83e5d2d3744105c756d22a1 to your computer and use it in GitHub Desktop.
File util methods for Android
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2018 OpenIntents.org | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import android.content.ContentUris; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.database.Cursor; | |
import android.database.DatabaseUtils; | |
import android.net.Uri; | |
import android.os.Build; | |
import android.os.Environment; | |
import android.provider.DocumentsContract; | |
import android.provider.MediaStore; | |
import android.provider.OpenableColumns; | |
import android.support.annotation.NonNull; | |
import android.support.annotation.Nullable; | |
import android.support.v4.content.FileProvider; | |
import android.util.Log; | |
import android.webkit.MimeTypeMap; | |
import okhttp3.ResponseBody; | |
import java.io.*; | |
import java.text.DecimalFormat; | |
import java.util.Comparator; | |
public class FileHelper { | |
public static final String DOCUMENTS_DIR = "documents"; | |
// configured android:authorities in AndroidManifest (https://developer.android.com/reference/android/support/v4/content/FileProvider) | |
public static final String AUTHORITY = "YOUR_AUTHORITY.provider"; | |
public static final String HIDDEN_PREFIX = "."; | |
private FileHelper() { | |
} //private constructor to enforce Singleton pattern | |
/** | |
* TAG for log messages. | |
*/ | |
static final String TAG = "FileHelper"; | |
private static final boolean DEBUG = false; // Set to true to enable logging | |
/** | |
* File and folder comparator. TODO Expose sorting option method | |
*/ | |
public static Comparator<File> sComparator = (f1, f2) -> { | |
// Sort alphabetically by lower case, which is much cleaner | |
return f1.getName().toLowerCase().compareTo( | |
f2.getName().toLowerCase()); | |
}; | |
/** | |
* File (not directories) filter. | |
*/ | |
public static FileFilter sFileFilter = file -> { | |
final String fileName = file.getName(); | |
// Return files only (not directories) and skip hidden files | |
return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX); | |
}; | |
/** | |
* Folder (directories) filter. | |
*/ | |
public static FileFilter sDirFilter = file -> { | |
final String fileName = file.getName(); | |
// Return directories only and skip hidden directories | |
return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX); | |
}; | |
/** | |
* 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. | |
*/ | |
public static String getExtension(String uri) { | |
if (uri == null) { | |
return null; | |
} | |
int dot = uri.lastIndexOf("."); | |
if (dot >= 0) { | |
return uri.substring(dot); | |
} else { | |
// No extension. | |
return ""; | |
} | |
} | |
/** | |
* @return Whether the URI is a local one. | |
*/ | |
public static boolean isLocal(String url) { | |
return url != null && !url.startsWith("http://") && !url.startsWith("https://"); | |
} | |
/** | |
* @return True if Uri is a MediaStore Uri. | |
* @author paulburke | |
*/ | |
public static boolean isMediaUri(Uri uri) { | |
return "media".equalsIgnoreCase(uri.getAuthority()); | |
} | |
/** | |
* Convert File into Uri. | |
* | |
* @param file | |
* @return uri | |
*/ | |
public static Uri getUri(File file) { | |
return (file != null) ? Uri.fromFile(file) : null; | |
} | |
/** | |
* Returns the path only (without file name). | |
* | |
* @param file | |
* @return | |
*/ | |
public static File getPathWithoutFilename(File file) { | |
if (file != null) { | |
if (file.isDirectory()) { | |
// no file to be split off. Return everything | |
return file; | |
} else { | |
String filename = file.getName(); | |
String filepath = file.getAbsolutePath(); | |
// Construct path without file name. | |
String pathwithoutname = filepath.substring(0, | |
filepath.length() - filename.length()); | |
if (pathwithoutname.endsWith("/")) { | |
pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1); | |
} | |
return new File(pathwithoutname); | |
} | |
} | |
return null; | |
} | |
/** | |
* @return The MIME type for the given file. | |
*/ | |
public static String getMimeType(File file) { | |
String extension = getExtension(file.getName()); | |
if (extension.length() > 0) | |
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); | |
return "application/octet-stream"; | |
} | |
/** | |
* @return The MIME type for the give Uri. | |
*/ | |
public static String getMimeType(Context context, Uri uri) { | |
File file = new File(getPath(context, uri)); | |
return getMimeType(file); | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is local. | |
*/ | |
public static boolean isLocalStorageDocument(Uri uri) { | |
return AUTHORITY.equals(uri.getAuthority()); | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is ExternalStorageProvider. | |
*/ | |
public static boolean isExternalStorageDocument(Uri uri) { | |
return "com.android.externalstorage.documents".equals(uri.getAuthority()); | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is DownloadsProvider. | |
*/ | |
public static boolean isDownloadsDocument(Uri uri) { | |
return "com.android.providers.downloads.documents".equals(uri.getAuthority()); | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is MediaProvider. | |
*/ | |
public static boolean isMediaDocument(Uri uri) { | |
return "com.android.providers.media.documents".equals(uri.getAuthority()); | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is Google Photos. | |
*/ | |
public static boolean isGooglePhotosUri(Uri uri) { | |
return "com.google.android.apps.photos.content".equals(uri.getAuthority()); | |
} | |
/** | |
* 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. | |
*/ | |
public static String getDataColumn(Context context, Uri uri, String selection, | |
String[] selectionArgs) { | |
Cursor cursor = null; | |
final String column = MediaStore.Files.FileColumns.DATA; | |
final String[] projection = { | |
column | |
}; | |
try { | |
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, | |
null); | |
if (cursor != null && cursor.moveToFirst()) { | |
if (DEBUG) | |
DatabaseUtils.dumpCursor(cursor); | |
final int column_index = cursor.getColumnIndexOrThrow(column); | |
return cursor.getString(column_index); | |
} | |
} finally { | |
if (cursor != null) | |
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> | |
* 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(String) | |
* @see #getFile(Context, Uri) | |
*/ | |
public static String getPath(final Context context, final Uri uri) { | |
if (DEBUG) | |
Log.d(TAG + " File -", | |
"Authority: " + uri.getAuthority() + | |
", Fragment: " + uri.getFragment() + | |
", Port: " + uri.getPort() + | |
", Query: " + uri.getQuery() + | |
", Scheme: " + uri.getScheme() + | |
", Host: " + uri.getHost() + | |
", Segments: " + uri.getPathSegments().toString() | |
); | |
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; | |
// DocumentProvider | |
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { | |
// LocalStorageProvider | |
if (isLocalStorageDocument(uri)) { | |
// The path is the id | |
return DocumentsContract.getDocumentId(uri); | |
} | |
// ExternalStorageProvider | |
else if (isExternalStorageDocument(uri)) { | |
final String docId = DocumentsContract.getDocumentId(uri); | |
final String[] split = docId.split(":"); | |
final String type = split[0]; | |
if ("primary".equalsIgnoreCase(type)) { | |
return Environment.getExternalStorageDirectory() + "/" + split[1]; | |
} | |
} | |
// DownloadsProvider | |
else if (isDownloadsDocument(uri)) { | |
final String id = DocumentsContract.getDocumentId(uri); | |
if (id != null && id.startsWith("raw:")) { | |
return id.substring(4); | |
} | |
String[] contentUriPrefixesToTry = new String[]{ | |
"content://downloads/public_downloads", | |
"content://downloads/my_downloads" | |
}; | |
for (String contentUriPrefix : contentUriPrefixesToTry) { | |
Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); | |
try { | |
String path = getDataColumn(context, contentUri, null, null); | |
if (path != null) { | |
return path; | |
} | |
} catch (Exception e) { | |
} | |
} | |
// path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams | |
String fileName = getFileName(context, uri); | |
File cacheDir = getDocumentCacheDir(context); | |
File file = generateFileName(fileName, cacheDir); | |
String destinationPath = null; | |
if (file != null) { | |
destinationPath = file.getAbsolutePath(); | |
saveFileFromUri(context, uri, destinationPath); | |
} | |
return destinationPath; | |
} | |
// MediaProvider | |
else if (isMediaDocument(uri)) { | |
final String docId = DocumentsContract.getDocumentId(uri); | |
final String[] split = docId.split(":"); | |
final String type = split[0]; | |
Uri contentUri = null; | |
if ("image".equals(type)) { | |
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; | |
} else if ("video".equals(type)) { | |
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; | |
} else if ("audio".equals(type)) { | |
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; | |
} | |
final String selection = "_id=?"; | |
final String[] selectionArgs = new String[]{ | |
split[1] | |
}; | |
return getDataColumn(context, contentUri, selection, selectionArgs); | |
} | |
} | |
// MediaStore (and general) | |
else if ("content".equalsIgnoreCase(uri.getScheme())) { | |
// Return the remote address | |
if (isGooglePhotosUri(uri)) { | |
return uri.getLastPathSegment(); | |
} | |
return getDataColumn(context, uri, null, null); | |
} | |
// File | |
else if ("file".equalsIgnoreCase(uri.getScheme())) { | |
return uri.getPath(); | |
} | |
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(Context, Uri) | |
*/ | |
public static File getFile(Context context, Uri uri) { | |
if (uri != null) { | |
String path = getPath(context, uri); | |
if (path != null && isLocal(path)) { | |
return new File(path); | |
} | |
} | |
return null; | |
} | |
/** | |
* Get the file size in a human-readable string. | |
* | |
* @param size | |
* @return | |
* @author paulburke | |
*/ | |
public static String getReadableFileSize(int size) { | |
final int BYTES_IN_KILOBYTES = 1024; | |
final DecimalFormat dec = new DecimalFormat("###.#"); | |
final String KILOBYTES = " KB"; | |
final String MEGABYTES = " MB"; | |
final String GIGABYTES = " GB"; | |
float fileSize = 0; | |
String suffix = KILOBYTES; | |
if (size > BYTES_IN_KILOBYTES) { | |
fileSize = size / BYTES_IN_KILOBYTES; | |
if (fileSize > BYTES_IN_KILOBYTES) { | |
fileSize = fileSize / BYTES_IN_KILOBYTES; | |
if (fileSize > BYTES_IN_KILOBYTES) { | |
fileSize = fileSize / BYTES_IN_KILOBYTES; | |
suffix = GIGABYTES; | |
} else { | |
suffix = MEGABYTES; | |
} | |
} | |
} | |
return String.valueOf(dec.format(fileSize) + suffix); | |
} | |
/** | |
* Get the Intent for selecting content to be used in an Intent Chooser. | |
* | |
* @return The intent for opening a file with Intent.createChooser() | |
*/ | |
public static Intent createGetContentIntent() { | |
// Implicitly allow the user to select a particular kind of data | |
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); | |
// The MIME data type filter | |
intent.setType("*/*"); | |
// Only return URIs that can be opened with ContentResolver | |
intent.addCategory(Intent.CATEGORY_OPENABLE); | |
return intent; | |
} | |
/** | |
* Creates View intent for given file | |
* | |
* @param file | |
* @return The intent for viewing file | |
*/ | |
public static Intent getViewIntent(Context context, File file) { | |
//Uri uri = Uri.fromFile(file); | |
Uri uri = FileProvider.getUriForFile(context, AUTHORITY, file); | |
Intent intent = new Intent(Intent.ACTION_VIEW); | |
String url = file.toString(); | |
if (url.contains(".doc") || url.contains(".docx")) { | |
// Word document | |
intent.setDataAndType(uri, "application/msword"); | |
} else if (url.contains(".pdf")) { | |
// PDF file | |
intent.setDataAndType(uri, "application/pdf"); | |
} else if (url.contains(".ppt") || url.contains(".pptx")) { | |
// Powerpoint file | |
intent.setDataAndType(uri, "application/vnd.ms-powerpoint"); | |
} else if (url.contains(".xls") || url.contains(".xlsx")) { | |
// Excel file | |
intent.setDataAndType(uri, "application/vnd.ms-excel"); | |
} else if (url.contains(".zip") || url.contains(".rar")) { | |
// WAV audio file | |
intent.setDataAndType(uri, "application/x-wav"); | |
} else if (url.contains(".rtf")) { | |
// RTF file | |
intent.setDataAndType(uri, "application/rtf"); | |
} else if (url.contains(".wav") || url.contains(".mp3")) { | |
// WAV audio file | |
intent.setDataAndType(uri, "audio/x-wav"); | |
} else if (url.contains(".gif")) { | |
// GIF file | |
intent.setDataAndType(uri, "image/gif"); | |
} else if (url.contains(".jpg") || url.contains(".jpeg") || url.contains(".png")) { | |
// JPG file | |
intent.setDataAndType(uri, "image/jpeg"); | |
} else if (url.contains(".txt")) { | |
// Text file | |
intent.setDataAndType(uri, "text/plain"); | |
} else if (url.contains(".3gp") || url.contains(".mpg") || url.contains(".mpeg") || | |
url.contains(".mpe") || url.contains(".mp4") || url.contains(".avi")) { | |
// Video files | |
intent.setDataAndType(uri, "video/*"); | |
} else { | |
intent.setDataAndType(uri, "*/*"); | |
} | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | |
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); | |
return intent; | |
} | |
public static File getDownloadsDir() { | |
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | |
} | |
public static File getDocumentCacheDir(@NonNull Context context) { | |
File dir = new File(context.getCacheDir(), DOCUMENTS_DIR); | |
if (!dir.exists()) { | |
dir.mkdirs(); | |
} | |
logDir(context.getCacheDir()); | |
logDir(dir); | |
return dir; | |
} | |
private static void logDir(File dir) { | |
if (!DEBUG) return; | |
Log.d(TAG, "Dir=" + dir); | |
File[] files = dir.listFiles(); | |
for (File file : files) { | |
Log.d(TAG, "File=" + file.getPath()); | |
} | |
} | |
@Nullable | |
public static File generateFileName(@Nullable String name, File directory) { | |
if (name == null) { | |
return null; | |
} | |
File file = new File(directory, name); | |
if (file.exists()) { | |
String fileName = name; | |
String extension = ""; | |
int dotIndex = name.lastIndexOf('.'); | |
if (dotIndex > 0) { | |
fileName = name.substring(0, dotIndex); | |
extension = name.substring(dotIndex); | |
} | |
int index = 0; | |
while (file.exists()) { | |
index++; | |
name = fileName + '(' + index + ')' + extension; | |
file = new File(directory, name); | |
} | |
} | |
try { | |
if (!file.createNewFile()) { | |
return null; | |
} | |
} catch (IOException e) { | |
Log.w(TAG, e); | |
return null; | |
} | |
logDir(directory); | |
return file; | |
} | |
/** | |
* Writes response body to disk | |
* | |
* @param body ResponseBody | |
* @param path file path | |
* @return File | |
*/ | |
public static File writeResponseBodyToDisk(ResponseBody body, String path) { | |
try { | |
File target = new File(path); | |
InputStream inputStream = null; | |
OutputStream outputStream = null; | |
try { | |
byte[] fileReader = new byte[4096]; | |
inputStream = body.byteStream(); | |
outputStream = new FileOutputStream(target); | |
while (true) { | |
int read = inputStream.read(fileReader); | |
if (read == -1) { | |
break; | |
} | |
outputStream.write(fileReader, 0, read); | |
} | |
outputStream.flush(); | |
return target; | |
} catch (IOException e) { | |
return null; | |
} finally { | |
if (inputStream != null) { | |
inputStream.close(); | |
} | |
if (outputStream != null) { | |
outputStream.close(); | |
} | |
} | |
} catch (IOException e) { | |
return null; | |
} | |
} | |
private static void saveFileFromUri(Context context, Uri uri, String destinationPath) { | |
InputStream is = null; | |
BufferedOutputStream bos = null; | |
try { | |
is = context.getContentResolver().openInputStream(uri); | |
bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false)); | |
byte[] buf = new byte[1024]; | |
is.read(buf); | |
do { | |
bos.write(buf); | |
} while (is.read(buf) != -1); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (is != null) is.close(); | |
if (bos != null) bos.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
public static byte[] readBytesFromFile(String filePath) { | |
FileInputStream fileInputStream = null; | |
byte[] bytesArray = null; | |
try { | |
File file = new File(filePath); | |
bytesArray = new byte[(int) file.length()]; | |
//read file into bytes[] | |
fileInputStream = new FileInputStream(file); | |
fileInputStream.read(bytesArray); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
if (fileInputStream != null) { | |
try { | |
fileInputStream.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
return bytesArray; | |
} | |
public static File createTempImageFile(Context context, String fileName) throws IOException { | |
// Create an image file name | |
File storageDir = new File(context.getCacheDir(), DOCUMENTS_DIR); | |
return File.createTempFile(fileName, ".jpg", storageDir); | |
} | |
public static String getFileName(@NonNull Context context, Uri uri) { | |
String mimeType = context.getContentResolver().getType(uri); | |
String filename = null; | |
if (mimeType == null && context != null) { | |
String path = getPath(context, uri); | |
if (path == null) { | |
filename = getName(uri.toString()); | |
} else { | |
File file = new File(path); | |
filename = file.getName(); | |
} | |
} else { | |
Cursor returnCursor = context.getContentResolver().query(uri, null, | |
null, null, null); | |
if (returnCursor != null) { | |
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); | |
returnCursor.moveToFirst(); | |
filename = returnCursor.getString(nameIndex); | |
returnCursor.close(); | |
} | |
} | |
return filename; | |
} | |
public static String getName(String filename) { | |
if (filename == null) { | |
return null; | |
} | |
int index = filename.lastIndexOf('/'); | |
return filename.substring(index + 1); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.ContentUris | |
import android.content.Context | |
import android.content.Intent | |
import android.database.Cursor | |
import android.database.DatabaseUtils | |
import android.net.Uri | |
import android.os.Build | |
import android.os.Environment | |
import android.provider.DocumentsContract | |
import android.provider.MediaStore | |
import android.provider.OpenableColumns | |
import android.support.v4.content.FileProvider | |
import android.webkit.MimeTypeMap | |
import okhttp3.ResponseBody | |
import timber.log.Timber | |
import java.io.BufferedOutputStream | |
import java.io.File | |
import java.io.FileInputStream | |
import java.io.FileOutputStream | |
import java.io.IOException | |
import java.io.InputStream | |
import java.io.OutputStream | |
import java.text.DecimalFormat | |
/* | |
* Copyright (C) 2018 OpenIntents.org | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
object FileHelperKt { | |
private const val DOCUMENTS_DIR = "documents" | |
// configured android:authorities in AndroidManifest (https://developer.android.com/reference/android/support/v4/content/FileProvider) | |
private const val AUTHORITY = "YOUR_AUTHORITY.provider" | |
private const val HIDDEN_PREFIX = "." | |
private val DEBUG = false // Set to true to enable logging | |
/** | |
* File and folder comparator. TODO Expose sorting option method | |
*/ | |
var sComparator = { f1: File, f2: File -> | |
// Sort alphabetically by lower case, which is much cleaner | |
f1.name.toLowerCase().compareTo( | |
f2.name.toLowerCase() | |
) | |
} | |
/** | |
* File (not directories) filter. | |
*/ | |
var sFileFilter = { file: File -> | |
val fileName = file.getName() | |
// Return files only (not directories) and skip hidden files | |
file.isFile() && !fileName.startsWith(HIDDEN_PREFIX) | |
} | |
/** | |
* Folder (directories) filter. | |
*/ | |
var sDirFilter = { file: File -> | |
val fileName = file.name | |
// Return directories only and skip hidden directories | |
file.isDirectory && !fileName.startsWith(HIDDEN_PREFIX) | |
} | |
val downloadsDir: File | |
get() = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) | |
/** | |
* 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. | |
*/ | |
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. | |
*/ | |
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? { | |
if (file != null) { | |
if (file.isDirectory) { | |
// no file to be split off. Return everything | |
return 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) | |
} | |
return File(pathwithoutname) | |
} | |
} | |
return null | |
} | |
/** | |
* @return The MIME type for the given file. | |
*/ | |
fun getMimeType(file: File): String? { | |
val extension = getExtension(file.name) | |
if (extension!!.isNotEmpty()) { | |
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)) | |
} else { | |
return "application/octet-stream" | |
} | |
} | |
/** | |
* @return The MIME type for the give Uri. | |
*/ | |
fun getMimeType(context: Context, uri: Uri): String? { | |
val file = File(getPath(context, uri)!!) | |
return getMimeType(file) | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is local. | |
*/ | |
fun isLocalStorageDocument(uri: Uri): Boolean { | |
return AUTHORITY == uri.authority | |
} | |
/** | |
* @param uri The Uri to check. | |
* @return Whether the Uri authority is ExternalStorageProvider. | |
*/ | |
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. | |
*/ | |
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. | |
*/ | |
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. | |
*/ | |
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. | |
*/ | |
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 columnIndex = cursor.getColumnIndexOrThrow(column) | |
return cursor.getString(columnIndex) | |
} | |
} 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 | |
*/ | |
fun getPath(context: Context, uri: Uri): String? { | |
Timber.d( | |
"[app] FileHelper - Authority: ${uri.authority}, " + | |
"Fragment: ${uri.fragment}, " + | |
"Port: ${uri.port}, " + | |
"Query: ${uri.query}, " + | |
"Scheme: ${uri.scheme}, " + | |
"Host: ${uri.host}, " + | |
"Segments: ${uri.pathSegments}" | |
) | |
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT | |
// DocumentProvider | |
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { | |
// LocalStorageProvider | |
if (isLocalStorageDocument(uri)) { | |
// The path is the id | |
return DocumentsContract.getDocumentId(uri) | |
} | |
// ExternalStorageProvider | |
else if (isExternalStorageDocument(uri)) { | |
val docId = DocumentsContract.getDocumentId(uri) | |
val split = docId.split((":").toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() | |
val type = split[0] | |
if ("primary".equals(type, ignoreCase = true)) { | |
return (Environment.getExternalStorageDirectory()).toString() + "/" + split[1] | |
} | |
} | |
// DownloadsProvider | |
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 (e: Exception) { | |
} | |
} | |
// 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 | |
} | |
// MediaProvider | |
else if (isMediaDocument(uri)) { | |
val docId = DocumentsContract.getDocumentId(uri) | |
val split = docId.split((":").toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() | |
val type = split[0] | |
var contentUri: Uri? = null | |
when (type) { | |
"image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI | |
"video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI | |
"audio" -> 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 | |
if (isGooglePhotosUri(uri)) { | |
return uri.lastPathSegment | |
} | |
return 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 | |
} | |
/** | |
* Get the file size in a human-readable string. | |
* | |
* @param size | |
* @return | |
* @author paulburke | |
*/ | |
fun getReadableFileSize(size: Int): String { | |
val bytesInKilobytes = 1024 | |
val dec = DecimalFormat("###.#") | |
val kb = " KB" | |
val mb = " MB" | |
val gb = " GB" | |
var fileSize = 0f | |
var suffix = kb | |
if (size > bytesInKilobytes) { | |
fileSize = (size / bytesInKilobytes).toFloat() | |
if (fileSize > bytesInKilobytes) { | |
fileSize /= bytesInKilobytes | |
if (fileSize > bytesInKilobytes) { | |
fileSize /= bytesInKilobytes | |
suffix = gb | |
} else { | |
suffix = mb | |
} | |
} | |
} | |
return (dec.format(fileSize.toDouble()) + suffix).toString() | |
} | |
/** | |
* Get the Intent for selecting content to be used in an Intent Chooser. | |
* | |
* @return The intent for opening a file with Intent.createChooser() | |
*/ | |
fun createGetContentIntent(): Intent { | |
// Implicitly allow the user to select a particular kind of data | |
val intent = Intent(Intent.ACTION_GET_CONTENT) | |
// The MIME data type filter | |
intent.type = "*/*" | |
// Only return URIs that can be opened with ContentResolver | |
intent.addCategory(Intent.CATEGORY_OPENABLE) | |
return intent | |
} | |
/** | |
* Creates View intent for given file | |
* | |
* @param file | |
* @return The intent for viewing file | |
*/ | |
fun getViewIntent(context: Context, file: File): Intent { | |
// Uri uri = Uri.fromFile(file); | |
val uri = FileProvider.getUriForFile(context, AUTHORITY, file) | |
val intent = Intent(Intent.ACTION_VIEW) | |
val url = file.toString() | |
if (url.contains(".doc") || url.contains(".docx")) { | |
// Word document | |
intent.setDataAndType(uri, "application/msword") | |
} else if (url.contains(".pdf")) { | |
// PDF file | |
intent.setDataAndType(uri, "application/pdf") | |
} else if (url.contains(".ppt") || url.contains(".pptx")) { | |
// Powerpoint file | |
intent.setDataAndType(uri, "application/vnd.ms-powerpoint") | |
} else if (url.contains(".xls") || url.contains(".xlsx")) { | |
// Excel file | |
intent.setDataAndType(uri, "application/vnd.ms-excel") | |
} else if (url.contains(".zip") || url.contains(".rar")) { | |
// WAV audio file | |
intent.setDataAndType(uri, "application/x-wav") | |
} else if (url.contains(".rtf")) { | |
// RTF file | |
intent.setDataAndType(uri, "application/rtf") | |
} else if (url.contains(".wav") || url.contains(".mp3")) { | |
// WAV audio file | |
intent.setDataAndType(uri, "audio/x-wav") | |
} else if (url.contains(".gif")) { | |
// GIF file | |
intent.setDataAndType(uri, "image/gif") | |
} else if (url.contains(".jpg") || url.contains(".jpeg") || url.contains(".png")) { | |
// JPG file | |
intent.setDataAndType(uri, "image/jpeg") | |
} else if (url.contains(".txt")) { | |
// Text file | |
intent.setDataAndType(uri, "text/plain") | |
} else if ((url.contains(".3gp") || url.contains(".mpg") || url.contains(".mpeg") || | |
url.contains(".mpe") || url.contains(".mp4") || url.contains(".avi")) | |
) { | |
// Video files | |
intent.setDataAndType(uri, "video/*") | |
} else { | |
intent.setDataAndType(uri, "*/*") | |
} | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | |
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) | |
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | |
return intent | |
} | |
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 | |
Timber.d("[app] FileHelper log dir=$dir") | |
val files = dir.listFiles() | |
for (file in files!!) { | |
Timber.d("[app] FileHelper path:${file.path}") | |
} | |
} | |
fun generateFileName(name: String?, directory: File): File? { | |
var name: String? = name ?: return 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) { | |
Timber.w(e, "[app] FileHelper") | |
return null | |
} | |
logDir(directory) | |
return file | |
} | |
/** | |
* Writes response body to disk | |
* | |
* @param body ResponseBody | |
* @param path file path | |
* @return File | |
*/ | |
fun writeResponseBodyToDisk(body: ResponseBody, path: String): File? { | |
try { | |
val target = File(path) | |
var inputStream: InputStream? = null | |
var outputStream: OutputStream? = null | |
try { | |
val fileReader = ByteArray(4096) | |
inputStream = body.byteStream() | |
outputStream = FileOutputStream(target) | |
while (true) { | |
val read = inputStream!!.read(fileReader) | |
if (read == -1) { | |
break | |
} | |
outputStream.write(fileReader, 0, read) | |
} | |
outputStream.flush() | |
return target | |
} catch (e: IOException) { | |
return null | |
} finally { | |
if (inputStream != null) { | |
inputStream.close() | |
} | |
if (outputStream != null) { | |
outputStream.close() | |
} | |
} | |
} catch (e: IOException) { | |
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() | |
} | |
} | |
} | |
fun readBytesFromFile(filePath: String): ByteArray? { | |
var fileInputStream: FileInputStream? = null | |
var bytesArray: ByteArray? = null | |
try { | |
val file = File(filePath) | |
bytesArray = ByteArray(file.length().toInt()) | |
// read file into bytes[] | |
fileInputStream = FileInputStream(file) | |
fileInputStream.read(bytesArray) | |
} catch (e: IOException) { | |
e.printStackTrace() | |
} finally { | |
if (fileInputStream != null) { | |
try { | |
fileInputStream.close() | |
} catch (e: IOException) { | |
e.printStackTrace() | |
} | |
} | |
} | |
return bytesArray | |
} | |
@Throws(IOException::class) | |
fun createTempImageFile(context: Context, fileName: String): File { | |
// Create an image file name | |
val storageDir = File(context.cacheDir, DOCUMENTS_DIR) | |
return File.createTempFile(fileName, ".jpg", storageDir) | |
} | |
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) | |
if (path == null) { | |
filename = getName(uri.toString()) | |
} else { | |
val file = File(path) | |
filename = 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 | |
} | |
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