diff --git a/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/CompressionTask.java b/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/CompressionTask.java new file mode 100644 index 0000000..61cba60 --- /dev/null +++ b/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/CompressionTask.java @@ -0,0 +1,168 @@ +package com.ahmednts.vivantor.filepicker; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.ref.WeakReference; + +/** + * Created by Kerollos Kromer on 17-Mar-19. + */ +public class CompressionTask extends AsyncTask { + + private static final String TAG = CompressionTask.class.getName(); + + private enum SupportedFormats { + JPG(".jpg"), + JPEG(".jpeg"), + PNG(".png"); + + private final String extension; + + SupportedFormats(String extension) { + this.extension = extension; + } + + @NonNull + @Override + public String toString() { + return this.extension; + } + } + + /** + * file length/size in bytes. + */ + private static final long MAX_SIZE = 2 * 1024 * 1024; // 2 MB + + private WeakReference contextWeakReference; + private CompressionListener compressionListener; + private boolean delete; + + private File file = null; + private boolean isOriginal; + + /** + * @param context context + * @param compressionListener compression process lifecycle callback + * @param delete set to true if you want to delete the new compressed file when you are done with it + */ + public CompressionTask(Context context, CompressionListener compressionListener, boolean delete) { + this.contextWeakReference = new WeakReference<>(context); + this.compressionListener = compressionListener; + this.delete = delete; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (compressionListener != null) compressionListener.onStart(); + } + + @Override + protected File doInBackground(File... files) { + Context context = contextWeakReference.get(); + if (context != null) { + try { + file = compressFile(context, files[0]); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + } + return file; + } + + @Override + protected void onPostExecute(File compressedFile) { + super.onPostExecute(compressedFile); + if (compressionListener != null && contextWeakReference.get() != null) { + if (compressedFile != null) { + compressionListener.onSuccess(compressedFile); + } else { + compressionListener.onFailure(); + } + } + } + + public interface CompressionListener { + void onStart(); + + void onSuccess(File compressedFile); + + void onFailure(); + } + + public void dispose() { + contextWeakReference.clear(); + compressionListener = null; + if (delete) deleteCompressedFile(); + } + + private void deleteCompressedFile() { + if (!isOriginal && file.exists() && file.isFile() && file.canWrite()) { + new Thread(() -> file.delete()).start(); + } + } + + /** + * we only compress if the file is bigger than {@value MAX_SIZE} , otherwise return original. + * + * @param originalFile file to be compressed {@link SupportedFormats}. + * @return original file if it is {@value MAX_SIZE} or less , otherwise compress and return the compressed file. + * @throws Exception if the file is not supported or some problem occurred during the process. + */ + @Nullable + private File compressFile(Context context, File originalFile) + throws Exception { + String fileExtension = VFileUtils.getExtension(originalFile.getPath()); + + if (!isValidFormat(fileExtension)) { + throw new Exception(fileExtension + " is not supported"); + } + + if (originalFile.length() <= MAX_SIZE) { + isOriginal = true; + return originalFile; + } + + Log.d(TAG, originalFile.length() + ""); + + Uri fileUri = VFileUtils.getUriForFile(context, originalFile); + Bitmap b = + VFileUtils.handleSamplingAndRotationBitmap(context, fileUri); + + Log.d(TAG, "Width :" + b.getWidth() + " Height :" + b.getHeight()); + + String fileName = "_compressed"; + File compressedFile = new File(context.getCacheDir(), fileName + fileExtension); + Log.d(TAG, compressedFile.getPath()); + Bitmap.CompressFormat compressFormat = + fileExtension.contains("png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; + + FileOutputStream out = new FileOutputStream(compressedFile); + b.compress(compressFormat, 100, out); + out.flush(); + out.close(); + + Log.d(TAG, compressedFile.length() + ""); + Log.d(TAG, "Width :" + b.getWidth() + " Height :" + b.getHeight()); + + return compressedFile; + } + + private boolean isValidFormat(String fileExtension) { + SupportedFormats[] supportedFormats = SupportedFormats.values(); + for (SupportedFormats supportedFormat : supportedFormats) { + if (supportedFormat.toString().equals(fileExtension)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFilePicker.java b/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFilePicker.java index fc5a2e0..00a5da4 100644 --- a/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFilePicker.java +++ b/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFilePicker.java @@ -9,7 +9,6 @@ import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; -import android.support.v4.content.FileProvider; import java.io.File; /** @@ -158,24 +157,14 @@ private void fromCameraWithFile(Activity context) { } if (requestPermissions(context, requestCode)) { - filePath = VFileUtils.GenerateFilePath(cameraDirectoryName, pickerType == IMAGE ? 1 : 3); + filePath = VFileUtils.generateFilePath(cameraDirectoryName, pickerType == IMAGE ? 1 : 3); if (filePath == null) return; - //Intent intent = new Intent(action); - //intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(filePath))); - //context.startActivityForResult(intent, requestCode); - Intent intent = new Intent(action); - Uri fileURI; - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { - fileURI = FileProvider.getUriForFile(context, - context.getApplicationContext().getPackageName() + ".fileprovider", - new File(filePath)); - } else { - fileURI = Uri.fromFile(new File(filePath)); - } + Uri fileURI = VFileUtils.getUriForFile(context, new File(filePath)); - intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, fileURI); + Intent intent = new Intent(action); + intent.putExtra(MediaStore.EXTRA_OUTPUT, fileURI); intent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (intent.resolveActivity(context.getPackageManager()) != null) { @@ -284,7 +273,8 @@ public void onRequestPermissions(Activity context, } } - public VFileInfo onActivityResult(Activity context, int requestCode, int resultCode, Intent data) { + public VFileInfo onActivityResult(Activity context, int requestCode, int resultCode, + Intent data) { if (resultCode == Activity.RESULT_OK) { if (requestCode != IMAGE_CAMERA_EXTERNAL && requestCode != VIDEO_CAMERA_EXTERNAL) { if (data.getData() == null) return null; @@ -298,6 +288,4 @@ public VFileInfo onActivityResult(Activity context, int requestCode, int resultC return null; } } -} - - +} \ No newline at end of file diff --git a/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFileUtils.java b/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFileUtils.java index 980c8cb..660585c 100644 --- a/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFileUtils.java +++ b/VivantorFilePicker-Library/src/main/java/com/ahmednts/vivantor/filepicker/VFileUtils.java @@ -12,17 +12,23 @@ import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; +import android.provider.OpenableColumns; import android.support.annotation.Nullable; +import android.support.v4.content.FileProvider; import android.util.Base64; import android.util.Log; import android.webkit.MimeTypeMap; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Date; +import java.util.Calendar; +import java.util.Locale; /** * Created by AhmedNTS on 2016-05-31. @@ -91,21 +97,17 @@ public static String getMimeType(File file) { } public static String getExtension(String uri) { - if (uri == null) { - return null; - } - - int dot = uri.lastIndexOf("."); - if (dot >= 0) { - return uri.substring(dot).toLowerCase(); - } else// No extension. - { - return ""; + if (uri != null) { + int dot = uri.lastIndexOf("."); + if (dot >= 0) { + return uri.substring(dot).toLowerCase(); + } } + return ""; } @Nullable - public static String GenerateFilePath(String appMediaFolderName, int type) { + public static String generateFilePath(String appMediaFolderName, int type) { // To be safe, you should check that the SDCard is mounted if (!Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)) { return null; @@ -113,23 +115,34 @@ public static String GenerateFilePath(String appMediaFolderName, int type) { if (appMediaFolderName == null || appMediaFolderName.isEmpty()) return null; - File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), appMediaFolderName); + File root = Environment.getExternalStorageDirectory(); + File appFolder = new File(root, appMediaFolderName); + File childFolder; + if (type == 1) { + childFolder = new File(appFolder, "Images"); + } else if (type == 2) { + childFolder = new File(appFolder, "Audios"); + } else if (type == 3) { + childFolder = new File(appFolder, "Videos"); + } else { + childFolder = appFolder; + } - if (!mediaStorageDir.exists()) { - if (!mediaStorageDir.mkdirs()) { + if (!childFolder.exists()) { + if (!childFolder.mkdirs()) { Log.d("GenerateFilePath", "failed to create directory"); return null; } } String filePath; - String timeStamp = SimpleDateFormat.getDateTimeInstance().format(new Date()); + String timeStamp = getCurrentDateTime(); if (type == 1) { - filePath = mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"; + filePath = childFolder.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"; } else if (type == 2) { - filePath = mediaStorageDir.getPath() + File.separator + "AUD_" + timeStamp + ".3gp"; + filePath = childFolder.getPath() + File.separator + "AUD_" + timeStamp + ".mp3"; } else if (type == 3) { - filePath = mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"; + filePath = childFolder.getPath() + File.separator + "VID_" + timeStamp + ".mp4"; } else { return null; } @@ -165,8 +178,12 @@ public static Bitmap handleSamplingAndRotationBitmap(Context context, Uri select options.inJustDecodeBounds = false; imageStream = context.getContentResolver().openInputStream(selectedImage); Bitmap img = BitmapFactory.decodeStream(imageStream, null, options); + imageStream.close(); - img = rotateImageIfRequired(img, selectedImage.getPath()); + String imagePath = getFilePathFromURI(context, selectedImage); + if (imagePath != null) { + img = rotateImageIfRequired(img, imagePath); + } return img; } @@ -198,7 +215,9 @@ private static int calculateInSampleSize(BitmapFactory.Options options, int reqW // Choose the smallest ratio as inSampleSize value, this will guarantee a final image // with both dimensions larger than or equal to the requested height and width. - inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + inSampleSize = heightRatio < widthRatio + ? heightRatio + : widthRatio; // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger @@ -267,11 +286,28 @@ public static String getFilePathFromURI(Context context, Uri uri) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } else if (VFileUtils.isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = - ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), - Long.valueOf(id)); - return getDataColumn(context, contentUri, null, null); + if (id != null && id.startsWith("raw:")) { + return id.substring(4); + } + + String[] contentUriPrefixesToTry = new String[] { + "content://downloads/public_downloads", "content://downloads/my_downloads", + "content://downloads/all_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) { + String path = copyToFileAndReturnPath(context, uri); + return path; + } + } } else if (VFileUtils.isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); @@ -289,7 +325,11 @@ public static String getFilePathFromURI(Context context, Uri uri) { String selection = "_id=?"; String[] selectionArgs = new String[] { split[1] }; - return getDataColumn(context, contentUri, selection, selectionArgs); + try { + return getDataColumn(context, contentUri, selection, selectionArgs); + } catch (Exception e) { + return null; + } } } // MediaStore @@ -298,7 +338,15 @@ else if ("content".equalsIgnoreCase(uri.getScheme())) { return uri.getLastPathSegment(); } - return getDataColumn(context, uri, null, null); + try { + String path = getDataColumn(context, uri, null, null); + if (path != null) { + return path; + } + } catch (Exception e) { + String path = copyToFileAndReturnPath(context, uri); + return path; + } } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { @@ -309,7 +357,7 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) { @Nullable public static String getDataColumn(Context context, Uri uri, String selection, - String[] selectionArgs) { + String[] selectionArgs) throws Exception { Cursor cursor = null; final String column = MediaStore.MediaColumns.DATA; final String[] projection = { column }; @@ -386,4 +434,128 @@ public static byte[] getFileByteArray(File file) { return byteArray; } -} + + private static String copyToFileAndReturnPath(Context context, Uri uri) { + // path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams + String fileName = getFileName(context, uri); + File file = new File(context.getCacheDir(), fileName); + String destinationPath = file.getAbsolutePath(); + saveFileFromUri(context, uri, destinationPath); + return destinationPath; + } + + 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(); + } + } + } + + @Nullable + public static File generateFileWithName(File directory, @Nullable String name) { + 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) { + return null; + } + + return file; + } + + public static String getFileName(Context context, Uri uri) { + String mimeType = context.getContentResolver().getType(uri); + String filename = null; + + if (mimeType == null) { + String path = getFilePathFromURI(context, uri); + if (path == null) { + filename = getFileName(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 getFileName(String filePath) { + if (filePath == null) { + return null; + } + int index = filePath.lastIndexOf('/'); + return filePath.substring(index + 1); + } + + public static String getCurrentDateTime() { + DateFormat dfDate = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH); + String date = dfDate.format(Calendar.getInstance().getTime()); + + DateFormat dfTime = new SimpleDateFormat("HHmmss", Locale.ENGLISH); + String time = dfTime.format(Calendar.getInstance().getTime()); + + return date + time; + } + + public static Uri getUriForFile(Context context, File file) { + Uri fileURI; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + fileURI = FileProvider.getUriForFile(context, + context.getApplicationContext().getPackageName() + ".fileprovider", + file); + } else { + fileURI = Uri.fromFile(file); + } + return fileURI; + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 038c2b4..ba847e2 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:3.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files