diff --git a/.gitignore b/.gitignore
index ba7e0945..9e83bef0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,8 @@
/.idea/gradle.xml
/.idea/runConfigurations.xml
/.idea/codeStyles
+/.idea/dictionaries
+/.idea/icon.png
.DS_Store
/build
/captures
diff --git a/LICENSE b/LICENSE
index 261eeb9e..4893b30f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2019-2020, Dhaval Patel
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index abf01e7f..b80dbdf8 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,10 @@
[](https://android-arsenal.com/api?level=19)

[]( https://android-arsenal.com/details/1/7510 )
+[](https://ktlint.github.io/)
[](https://github.com/Dhaval2404/ImagePicker)
-[](https://saythanks.io/to/Dhaval2404)
+[](https://opensource.org/licenses/Apache-2.0)
+[](https://github.com/Dhaval2404/ImagePicker/blob/master/LICENSE)
[](https://twitter.com/intent/tweet?text=Check+out+an+ImagePicker+library+to+Pick+an+image+from+the+Gallery+or+Capture+an+image+with+Camera.+https%3A%2F%2Fgithub.com%2FDhaval2404%2FImagePicker+%40dhaval2404+%23Android+%23Kotlin+%23AndroidDev)
@@ -321,7 +323,7 @@ We'll be really happy if you sent us links to your projects where you use our co
## License
- Copyright 2019, The Android Open Source Project
+ Copyright 2019-2020, Dhaval Patel
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle
index 719f6987..25ed3cda 100644
--- a/imagepicker/build.gradle
+++ b/imagepicker/build.gradle
@@ -7,12 +7,12 @@ apply plugin: 'kotlin-android-extensions'
apply from: "../ktlint.gradle"
android {
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
minSdkVersion 19
- targetSdkVersion 28
+ targetSdkVersion 29
versionCode 9
versionName "1.7.1"
@@ -46,7 +46,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation "androidx.exifinterface:exifinterface:1.1.0"
+ implementation "androidx.exifinterface:exifinterface:1.2.0"
+ implementation 'androidx.documentfile:documentfile:1.0.1'
//More Info: https://github.com/Yalantis/uCrop
implementation 'com.github.yalantis:ucrop:2.2.4'
diff --git a/imagepicker/src/main/AndroidManifest.xml b/imagepicker/src/main/AndroidManifest.xml
index d7baeee7..b940fe9c 100644
--- a/imagepicker/src/main/AndroidManifest.xml
+++ b/imagepicker/src/main/AndroidManifest.xml
@@ -1,9 +1,6 @@
-
-
-
mCropProvider.startIntent(file)
- mCompressionProvider.isCompressionRequired(file) -> mCompressionProvider.compress(file)
- else -> setResult(file)
+ mCropProvider.isCropEnabled() -> mCropProvider.startIntent(uri)
+ mCompressionProvider.isCompressionRequired(uri) -> mCompressionProvider.compress(uri)
+ else -> setResult(uri)
}
}
@@ -160,54 +137,47 @@ class ImagePickerActivity : AppCompatActivity() {
*
* Check if compression is enable/required. If yes then start compression else return result.
*
- * @param file Crop image file
+ * @param uri Crop image uri
*/
- fun setCropImage(file: File) {
- mCropFile = file
-
- mCameraProvider?.let {
- // Delete Camera file after crop. Else there will be two image for the same action.
- // In case of Gallery Provider, we will get original image path, so we will not delete that.
- mImageFile?.delete()
- mImageFile = null
- }
+ fun setCropImage(uri: Uri) {
+ // Delete Camera file after crop. Else there will be two image for the same action.
+ // In case of Gallery Provider, we will get original image path, so we will not delete that.
+ mCameraProvider?.delete()
- if (mCompressionProvider.isCompressionRequired(file)) {
- mCompressionProvider.compress(file)
+ if (mCompressionProvider.isCompressionRequired(uri)) {
+ mCompressionProvider.compress(uri)
} else {
- setResult(file)
+ setResult(uri)
}
}
/**
* {@link CompressionProvider} Result will be available here.
*
- * @param file Compressed image file
+ * @param uri Compressed image Uri
*/
- fun setCompressedImage(file: File) {
+ fun setCompressedImage(uri: Uri) {
// This is the case when Crop is not enabled
- mCameraProvider?.let {
- // Delete Camera file after Compress. Else there will be two image for the same action.
- // In case of Gallery Provider, we will get original image path, so we will not delete that.
- mImageFile?.delete()
- }
+
+ // Delete Camera file after crop. Else there will be two image for the same action.
+ // In case of Gallery Provider, we will get original image path, so we will not delete that.
+ mCameraProvider?.delete()
// If crop file is not null, Delete it after crop
- mCropFile?.delete()
- mCropFile = null
+ mCropProvider.delete()
- setResult(file)
+ setResult(uri)
}
/**
* Set Result, Image is successfully capture/picked/cropped/compressed.
*
- * @param file final image file
+ * @param uri final image Uri
*/
- private fun setResult(file: File) {
+ private fun setResult(uri: Uri) {
val intent = Intent()
- intent.data = Uri.fromFile(file)
- intent.putExtra(ImagePicker.EXTRA_FILE_PATH, file.absolutePath)
+ intent.data = uri
+ intent.putExtra(ImagePicker.EXTRA_FILE_PATH, FileUriUtils.getRealPath(this, uri))
setResult(Activity.RESULT_OK, intent)
finish()
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerFileProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerFileProvider.kt
index bfcc59ae..206ecd80 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerFileProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerFileProvider.kt
@@ -1,4 +1,4 @@
-package com.github.dhaval2404.imagepicker;
+package com.github.dhaval2404.imagepicker
import androidx.core.content.FileProvider
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt
index b4ba8bff..e5b85bfd 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt
@@ -2,8 +2,9 @@ package com.github.dhaval2404.imagepicker.provider
import android.content.ContextWrapper
import android.os.Bundle
-import android.widget.Toast
+import android.os.Environment
import com.github.dhaval2404.imagepicker.ImagePickerActivity
+import java.io.File
/**
* Abstract Provider class
@@ -12,7 +13,13 @@ import com.github.dhaval2404.imagepicker.ImagePickerActivity
* @version 1.0
* @since 04 January 2019
*/
-abstract class BaseProvider(protected val activity: ImagePickerActivity) : ContextWrapper(activity) {
+abstract class BaseProvider(protected val activity: ImagePickerActivity) :
+ ContextWrapper(activity) {
+
+ fun getFileDir(path: String?): File {
+ return if (path != null) File(path)
+ else getExternalFilesDir(Environment.DIRECTORY_DCIM)!!
+ }
/**
* Cancel operation and Set Error Message
@@ -33,15 +40,6 @@ abstract class BaseProvider(protected val activity: ImagePickerActivity) : Conte
setError(getString(errorRes))
}
- /**
- * Show Short Toast Message
- *
- * @param messageRes String message resource
- */
- protected fun showToast(messageRes: Int) {
- Toast.makeText(this, messageRes, Toast.LENGTH_SHORT).show()
- }
-
/**
* Call this method when task is cancel in between the operation.
* E.g. user hit back-press
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CameraProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CameraProvider.kt
index fb253b9d..2333b156 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CameraProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CameraProvider.kt
@@ -4,6 +4,7 @@ import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import androidx.core.app.ActivityCompat.requestPermissions
import com.github.dhaval2404.imagepicker.ImagePicker
@@ -63,16 +64,14 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
/**
* Camera image will be stored in below file directory
*/
- private var mFileDir: File? = null
+ private val mFileDir: File
init {
val bundle = activity.intent.extras!!
// Get File Directory
val fileDir = bundle.getString(ImagePicker.EXTRA_SAVE_DIRECTORY)
- fileDir?.let {
- mFileDir = File(it)
- }
+ mFileDir = getFileDir(fileDir)
}
/**
@@ -99,10 +98,17 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
}
/**
- * Start Camera Capture Intent
+ * Start Camera Intent
+ *
+ * Create Temporary File object and Pass it to Camera Intent
*/
fun startIntent() {
- checkPermission()
+ if (!IntentUtils.isCameraAppAvailable(this)) {
+ setError(R.string.error_camera_app_not_found)
+ return
+ }
+
+ startCameraIntent()
}
/**
@@ -127,7 +133,7 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
*/
private fun startCameraIntent() {
// Create and get empty file to store capture image content
- val file = FileUtil.getImageFile(dir = mFileDir)
+ val file = FileUtil.getImageFile(fileDir = mFileDir)
mCameraFile = file
// Check if file exists
@@ -147,7 +153,7 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
// Check again if permission is granted
if (isPermissionGranted(this)) {
// Permission is granted, Start Camera Intent
- startCameraIntent()
+ startIntent()
} else {
// Exit with error message
val errorRes = if (mAskCameraPermission) {
@@ -170,7 +176,7 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == CAMERA_INTENT_REQ_CODE) {
if (resultCode == Activity.RESULT_OK) {
- handleResult(data)
+ handleResult()
} else {
setResultCancel()
}
@@ -180,15 +186,15 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
/**
* This method will be called when final result fot this provider is enabled.
*/
- private fun handleResult(data: Intent?) {
- activity.setImage(mCameraFile!!)
+ private fun handleResult() {
+ activity.setImage(Uri.fromFile(mCameraFile))
}
/**
* Delete Camera file is exists
*/
override fun onFailure() {
- mCameraFile?.delete()
+ delete()
}
/**
@@ -224,4 +230,14 @@ class CameraProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
}
return false
}
+
+ /**
+ * Delete Camera File, If not required
+ *
+ * After Camera Image Crop/Compress Original File will not required
+ */
+ fun delete() {
+ mCameraFile?.delete()
+ mCameraFile = null
+ }
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CompressionProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CompressionProvider.kt
index afa6cbf2..f7f8f7c9 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CompressionProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CompressionProvider.kt
@@ -2,7 +2,7 @@ package com.github.dhaval2404.imagepicker.provider
import android.annotation.SuppressLint
import android.graphics.Bitmap
-import android.graphics.BitmapFactory
+import android.net.Uri
import android.os.AsyncTask
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.ImagePickerActivity
@@ -27,8 +27,7 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
private val mMaxHeight: Int
private val mMaxFileSize: Long
- private var mOriginalFile: File? = null
- private var mFileDir: File? = null
+ private val mFileDir: File
init {
val bundle = activity.intent.extras!!
@@ -42,9 +41,7 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
// Get File Directory
val fileDir = bundle.getString(ImagePicker.EXTRA_SAVE_DIRECTORY)
- fileDir?.let {
- mFileDir = File(it)
- }
+ mFileDir = getFileDir(fileDir)
}
/**
@@ -52,7 +49,7 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
*
* @return Boolean. True if Compression should be enabled else false.
*/
- fun isCompressEnabled(): Boolean {
+ private fun isCompressEnabled(): Boolean {
return mMaxFileSize > 0L
}
@@ -60,12 +57,26 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
* Check if compression is required
* @param file File object to apply Compression
*/
- fun isCompressionRequired(file: File): Boolean {
+ private fun isCompressionRequired(file: File): Boolean {
val status = isCompressEnabled() && getSizeDiff(file) > 0L
if (!status && mMaxWidth > 0 && mMaxHeight > 0) {
// Check image resolution
- val sizes = getImageSize(file)
- return sizes[0] > mMaxWidth || sizes[1] > mMaxHeight
+ val resolution = FileUtil.getImageResolution(file)
+ return resolution.first > mMaxWidth || resolution.second > mMaxHeight
+ }
+ return status
+ }
+
+ /**
+ * Check if compression is required
+ * @param uri Uri object to apply Compression
+ */
+ fun isCompressionRequired(uri: Uri): Boolean {
+ val status = isCompressEnabled() && getSizeDiff(uri) > 0L
+ if (!status && mMaxWidth > 0 && mMaxHeight > 0) {
+ // Check image resolution
+ val resolution = FileUtil.getImageResolution(this, uri)
+ return resolution.first > mMaxWidth || resolution.second > mMaxHeight
}
return status
}
@@ -74,25 +85,30 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
return file.length() - mMaxFileSize
}
+ private fun getSizeDiff(uri: Uri): Long {
+ val length = FileUtil.getImageSize(this, uri)
+ return length - mMaxFileSize
+ }
+
/**
* Compress given file if enabled.
*
- * @param file File to compress
+ * @param uri Uri to compress
*/
- fun compress(file: File) {
- startCompressionWorker(file)
+ fun compress(uri: Uri) {
+ startCompressionWorker(uri)
}
/**
* Start Compression in Background
*/
@SuppressLint("StaticFieldLeak")
- private fun startCompressionWorker(file: File) {
- mOriginalFile = file
- object : AsyncTask() {
- override fun doInBackground(vararg params: File): File? {
+ private fun startCompressionWorker(uri: Uri) {
+ object : AsyncTask() {
+ override fun doInBackground(vararg params: Uri): File? {
// Perform operation in background
- return startCompression(params[0])
+ val file = FileUtil.getTempFile(this@CompressionProvider, params[0]) ?: return null
+ return startCompression(file)
}
override fun onPostExecute(file: File?) {
@@ -105,7 +121,7 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
setError(com.github.dhaval2404.imagepicker.R.string.error_failed_to_compress_image)
}
}
- }.execute(file)
+ }.execute(uri)
}
/**
@@ -175,7 +191,7 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
quality = 100
}
- val compressFile: File? = FileUtil.getImageFile(dir = mFileDir)
+ val compressFile: File? = FileUtil.getImageFile(fileDir = mFileDir)
return if (compressFile != null) {
ImageUtil.compressImage(
file, maxWidth.toFloat(), maxHeight.toFloat(),
@@ -214,18 +230,6 @@ class CompressionProvider(activity: ImagePickerActivity) : BaseProvider(activity
* This method will be called when final result fot this provider is enabled.
*/
private fun handleResult(file: File) {
- activity.setCompressedImage(file)
- }
-
- /**
- *
- * @param file File to get Image Size
- * @return Int Array, Index 0 has width and Index 1 has height
- */
- private fun getImageSize(file: File): IntArray {
- val options = BitmapFactory.Options()
- options.inJustDecodeBounds = true
- BitmapFactory.decodeFile(file.absolutePath, options)
- return intArrayOf(options.outWidth, options.outHeight)
+ activity.setCompressedImage(Uri.fromFile(file))
}
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CropProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CropProvider.kt
index c4cf7131..63e6d5f2 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CropProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/CropProvider.kt
@@ -39,7 +39,7 @@ class CropProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
private val mCropAspectX: Float
private val mCropAspectY: Float
private var mCropImageFile: File? = null
- private var mFileDir: File? = null
+ private val mFileDir: File
init {
val bundle = activity.intent.extras!!
@@ -55,9 +55,7 @@ class CropProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
// Get File Directory
val fileDir = bundle.getString(ImagePicker.EXTRA_SAVE_DIRECTORY)
- fileDir?.let {
- mFileDir = File(it)
- }
+ mFileDir = getFileDir(fileDir)
}
/**
@@ -93,17 +91,17 @@ class CropProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
/**
* Start Crop Activity
*/
- fun startIntent(file: File) {
- cropImage(file)
+ fun startIntent(uri: Uri) {
+ cropImage(uri)
}
/**
- * @param file Image File to be cropped
+ * @param uri Uri to be cropped
* @throws IOException if failed to crop image
*/
@Throws(IOException::class)
- private fun cropImage(file: File) {
- mCropImageFile = FileUtil.getImageFile(dir = mFileDir)
+ private fun cropImage(uri: Uri) {
+ mCropImageFile = FileUtil.getImageFile(fileDir = mFileDir)
if (mCropImageFile == null || !mCropImageFile!!.exists()) {
Log.e(TAG, "Failed to create crop image file")
@@ -112,7 +110,7 @@ class CropProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
}
val options = UCrop.Options()
- val uCrop = UCrop.of(Uri.fromFile(file), Uri.fromFile(mCropImageFile))
+ val uCrop = UCrop.of(uri, Uri.fromFile(mCropImageFile))
.withOptions(options)
if (mCropAspectX > 0 && mCropAspectY > 0) {
@@ -162,16 +160,26 @@ class CropProvider(activity: ImagePickerActivity) : BaseProvider(activity) {
*/
private fun handleResult(file: File?) {
if (file != null) {
- activity.setCropImage(file)
+ activity.setCropImage(Uri.fromFile(file))
} else {
setError(R.string.error_failed_to_crop_image)
}
}
/**
- * Delete Crop file is exists
+ * Handle Crop Failed
*/
override fun onFailure() {
+ delete()
+ }
+
+ /**
+ * Delete Crop File, If not required
+ *
+ * After Image Compression, Crop File will not required
+ */
+ fun delete() {
mCropImageFile?.delete()
+ mCropImageFile = null
}
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt
index 2ca327bb..c5e16dd1 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt
@@ -7,10 +7,8 @@ import androidx.core.app.ActivityCompat.requestPermissions
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.ImagePickerActivity
import com.github.dhaval2404.imagepicker.R
-import com.github.dhaval2404.imagepicker.util.FileUriUtils
import com.github.dhaval2404.imagepicker.util.IntentUtils
import com.github.dhaval2404.imagepicker.util.PermissionUtil
-import java.io.File
/**
* Select image from Storage
@@ -28,13 +26,13 @@ class GalleryProvider(activity: ImagePickerActivity) :
* to crop or compress image write permission is also required. as both permission is in
* same group, we have used write permission here.
*/
- private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
private const val GALLERY_INTENT_REQ_CODE = 4261
private const val PERMISSION_INTENT_REQ_CODE = 4262
}
- //Mime types restrictions for gallery. By default all mime types are valid
+ // Mime types restrictions for gallery. By default all mime types are valid
private val mimeTypes: Array
init {
@@ -47,7 +45,7 @@ class GalleryProvider(activity: ImagePickerActivity) :
* Start Gallery Capture Intent
*/
fun startIntent() {
- checkPermission()
+ startGalleryIntent()
}
/**
@@ -110,12 +108,7 @@ class GalleryProvider(activity: ImagePickerActivity) :
private fun handleResult(data: Intent?) {
val uri = data?.data
if (uri != null) {
- val filePath: String? = FileUriUtils.getRealPath(activity, uri)
- if (!filePath.isNullOrEmpty()) {
- activity.setImage(File(filePath))
- } else {
- setError(R.string.error_failed_pick_gallery_image)
- }
+ activity.setImage(uri)
} else {
setError(R.string.error_failed_pick_gallery_image)
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUriUtils.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUriUtils.kt
index 66283f67..2f9a1781 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUriUtils.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUriUtils.kt
@@ -25,6 +25,10 @@ import java.io.OutputStream
object FileUriUtils {
+ fun isFileUri(uri: Uri): Boolean {
+ return "file".equals(uri.scheme, ignoreCase = true)
+ }
+
fun getRealPath(context: Context, uri: Uri): String? {
var path = getPathFromLocalUri(context, uri)
if (path == null) {
@@ -64,7 +68,8 @@ object FileUriUtils {
} else if (isDownloadsDocument(uri)) {
val fileName = getFilePath(context, uri)
if (fileName != null) {
- return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
+ return Environment.getExternalStorageDirectory()
+ .toString() + "/Download/" + fileName
}
val id = DocumentsContract.getDocumentId(uri)
@@ -95,7 +100,12 @@ object FileUriUtils {
} else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
// Return the remote address
- return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null)
+ return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(
+ context,
+ uri,
+ null,
+ null
+ )
} else if ("file".equals(uri.scheme!!, ignoreCase = true)) {
return uri.path
} // File
@@ -116,7 +126,8 @@ object FileUriUtils {
val projection = arrayOf(column)
try {
- cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
+ cursor =
+ context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(index)
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUtil.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUtil.kt
index a2c88590..c5b2f887 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUtil.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/FileUtil.kt
@@ -1,8 +1,13 @@
package com.github.dhaval2404.imagepicker.util
-import android.os.Environment
+import android.content.Context
+import android.graphics.BitmapFactory
+import android.net.Uri
import android.os.StatFs
+import androidx.documentfile.provider.DocumentFile
import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
@@ -22,25 +27,23 @@ object FileUtil {
*
* Default it will take Camera folder as it's directory
*
- * @param dir File Folder in which file needs tobe created.
+ * @param fileDir File Folder in which file needs tobe created.
* @param extension String Image file extension.
* @return Return Empty file to store camera image.
* @throws IOException if permission denied of failed to create new file.
*/
- fun getImageFile(dir: File? = null, extension: String? = null): File? {
+ fun getImageFile(fileDir: File, extension: String? = null): File? {
try {
// Create an image file name
val ext = extension ?: ".jpg"
- val imageFileName = "IMG_${getTimestamp()}$ext"
-
- // Create File Directory Object
- val storageDir = dir ?: getCameraDirectory()
+ val fileName = getFileName()
+ val imageFileName = "$fileName$ext"
// Create Directory If not exist
- if (!storageDir.exists()) storageDir.mkdirs()
+ if (!fileDir.exists()) fileDir.mkdirs()
// Create File Object
- val file = File(storageDir, imageFileName)
+ val file = File(fileDir, imageFileName)
// Create empty file
file.createNewFile()
@@ -52,15 +55,8 @@ object FileUtil {
}
}
- /**
- * Get Camera Image Directory
- *
- * @return File Camera Image Directory
- */
- private fun getCameraDirectory(): File {
- val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
- return File(dir, "Camera")
- }
+ private fun getFileName() = "IMG_${getTimestamp()}"
+ // private fun getFileName() = "IMAGE_PICKER"
/**
* Get Current Time in yyyyMMdd HHmmssSSS format
@@ -83,4 +79,88 @@ object FileUtil {
val blockSize = stat.blockSizeLong
return availBlocks * blockSize
}
+
+ /**
+ * Get Image Width & Height from Uri
+ *
+ * @param uri Uri to get Image Size
+ * @return Int Array, Index 0 has width and Index 1 has height
+ */
+ fun getImageResolution(context: Context, uri: Uri): Pair {
+ val options = BitmapFactory.Options()
+ options.inJustDecodeBounds = true
+ val stream = context.contentResolver.openInputStream(uri)
+ BitmapFactory.decodeStream(stream, null, options)
+ return Pair(options.outWidth, options.outHeight)
+ }
+
+ /**
+ * Get Image Width & Height from File
+ *
+ * @param file File to get Image Size
+ * @return Int Array, Index 0 has width and Index 1 has height
+ */
+ fun getImageResolution(file: File): Pair {
+ val options = BitmapFactory.Options()
+ options.inJustDecodeBounds = true
+ BitmapFactory.decodeFile(file.absolutePath, options)
+ return Pair(options.outWidth, options.outHeight)
+ }
+
+ /**
+ * Get Image File Size
+ *
+ * @param uri Uri to get Image Size
+ * @return Int Image File Size
+ */
+ fun getImageSize(context: Context, uri: Uri): Long {
+ return getDocumentFile(context, uri)?.length() ?: 0
+ }
+
+ /**
+ * Create copy of Uri into application specific local path
+ *
+ * @param context Application Context
+ * @param uri Source Uri
+ * @return File return copy of Uri object
+ */
+ fun getTempFile(context: Context, uri: Uri): File? {
+ try {
+ val destination = File(context.cacheDir, "image_picker.png")
+
+ val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")
+ val fileDescriptor = parcelFileDescriptor?.fileDescriptor ?: return null
+
+ val src = FileInputStream(fileDescriptor).channel
+ val dst = FileOutputStream(destination).channel
+ dst.transferFrom(src, 0, src.size())
+ src.close()
+ dst.close()
+
+ return destination
+ } catch (ex: IOException) {
+ ex.printStackTrace()
+ }
+ return null
+ }
+
+ /**
+ * Get DocumentFile from Uri
+ *
+ * @param context Application Context
+ * @param uri Source Uri
+ * @return DocumentFile return DocumentFile from Uri
+ */
+ fun getDocumentFile(context: Context, uri: Uri): DocumentFile? {
+ var file: DocumentFile? = null
+ if (FileUriUtils.isFileUri(uri)) {
+ val path = FileUriUtils.getRealPath(context, uri)
+ if (path != null) {
+ file = DocumentFile.fromFile(File(path))
+ }
+ } else {
+ file = DocumentFile.fromSingleUri(context, uri)
+ }
+ return file
+ }
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/ImageUtil.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/ImageUtil.kt
index 4c183c29..74de5c26 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/ImageUtil.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/ImageUtil.kt
@@ -194,7 +194,10 @@ object ImageUtil {
/**
* Ref: https://developer.android.com/topic/performance/graphics/manage-memory#kotlin
*/
- private fun canUseForInBitmap(candidate: Bitmap, targetOptions: BitmapFactory.Options): Boolean {
+ private fun canUseForInBitmap(
+ candidate: Bitmap,
+ targetOptions: BitmapFactory.Options
+ ): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/IntentUtils.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/IntentUtils.kt
index 3c3f5206..7e72d3ce 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/IntentUtils.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/IntentUtils.kt
@@ -7,6 +7,7 @@ import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.core.content.FileProvider
+import androidx.documentfile.provider.DocumentFile
import com.github.dhaval2404.imagepicker.R
import java.io.File
@@ -67,7 +68,8 @@ object IntentUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// authority = com.github.dhaval2404.imagepicker.provider
- val authority = context.packageName + context.getString(R.string.image_picker_provider_authority_suffix)
+ val authority =
+ context.packageName + context.getString(R.string.image_picker_provider_authority_suffix)
val photoURI = FileProvider.getUriForFile(context, authority, file)
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
} else {
@@ -77,7 +79,43 @@ object IntentUtils {
return intent
}
+ /**
+ * Check if Camera App is available or not
+ *
+ * @return true if Camera App is Available else return false
+ */
+ fun isCameraAppAvailable(context: Context): Boolean {
+ val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ return intent.resolveActivity(context.packageManager) != null
+ }
+
fun isCameraHardwareAvailable(context: Context): Boolean {
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
}
+
+ /**
+ * Get Intent to View Uri backed File
+ *
+ * @param context
+ * @param uri
+ * @return Intent
+ */
+ fun getUriViewIntent(context: Context, uri: Uri): Intent {
+ val intent = Intent(Intent.ACTION_VIEW)
+ val authority =
+ context.packageName + context.getString(R.string.image_picker_provider_authority_suffix)
+
+ val file = DocumentFile.fromSingleUri(context, uri)
+ val dataUri = if (file?.canRead() == true) {
+ uri
+ } else {
+ val filePath = FileUriUtils.getRealPath(context, uri)!!
+ FileProvider.getUriForFile(context, authority, File(filePath))
+ }
+
+ intent.setDataAndType(dataUri, "image/*")
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+
+ return intent
+ }
}
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/PermissionUtil.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/PermissionUtil.kt
index a9926bf1..8513748f 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/PermissionUtil.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/PermissionUtil.kt
@@ -48,7 +48,10 @@ object PermissionUtil {
* @return true if permission defined in AndroidManifest.xml file, else return false.
*/
fun isPermissionInManifest(context: Context, permission: String): Boolean {
- val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
+ val packageInfo = context.packageManager.getPackageInfo(
+ context.packageName,
+ PackageManager.GET_PERMISSIONS
+ )
val permissions = packageInfo.requestedPermissions
if (permissions.isNullOrEmpty())
diff --git a/imagepicker/src/main/res/values/strings.xml b/imagepicker/src/main/res/values/strings.xml
index 68693a04..cf78f16b 100644
--- a/imagepicker/src/main/res/values/strings.xml
+++ b/imagepicker/src/main/res/values/strings.xml
@@ -23,5 +23,7 @@
Failed to compress image
Task Cancelled
+ Camera app not found
+
diff --git a/sample/build.gradle b/sample/build.gradle
index 220e7922..a1b867c8 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -7,11 +7,11 @@ apply plugin: 'kotlin-android-extensions'
apply from: "../ktlint.gradle"
android {
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
applicationId "com.github.dhaval2404.imagepicker.sample"
minSdkVersion 19
- targetSdkVersion 28
+ targetSdkVersion 29
versionCode 9
versionName "1.7.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -41,6 +41,7 @@ dependencies {
implementation 'androidx.browser:browser:1.2.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.github.florent37:inline-activity-result-kotlin:1.0.3'
+ implementation 'androidx.documentfile:documentfile:1.0.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
@@ -50,7 +51,7 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.11.0'
//Leakcanary
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
+ //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
implementation project(':imagepicker')
diff --git a/sample/src/androidTest/java/com/github/dhaval2404/imagepicker/MainActivityEspressoTest.kt b/sample/src/androidTest/java/com/github/dhaval2404/imagepicker/MainActivityEspressoTest.kt
index 10cf077f..24cfe7d8 100644
--- a/sample/src/androidTest/java/com/github/dhaval2404/imagepicker/MainActivityEspressoTest.kt
+++ b/sample/src/androidTest/java/com/github/dhaval2404/imagepicker/MainActivityEspressoTest.kt
@@ -9,7 +9,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import com.github.dhaval2404.imagepicker.sample.MainActivity
import com.github.dhaval2404.imagepicker.sample.R
-import kotlinx.android.synthetic.main.content_gallery_only.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index db9503af..622c2439 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -2,8 +2,6 @@
-
-
{
IntentUtil.openURL(this, GITHUB_REPOSITORY)
return true
@@ -60,12 +62,13 @@ class MainActivity : AppCompatActivity() {
fun pickProfileImage(view: View) {
ImagePicker.with(this)
// Crop Square image
- .cropSquare()
+ //.cropSquare()
.setImageProviderInterceptor { imageProvider -> // Intercept ImageProvider
- Log.d("ImagePicker", "Selected ImageProvider: "+imageProvider.name)
+ Log.d("ImagePicker", "Selected ImageProvider: " + imageProvider.name)
}
// Image resolution will be less than 512 x 512
- .maxResultSize(512, 512)
+ //.maxResultSize(512, 512)
+ //.saveDir(getExternalFilesDir(null)!!)
.start(PROFILE_IMAGE_REQ_CODE)
}
@@ -76,7 +79,7 @@ class MainActivity : AppCompatActivity() {
// User can only select image from Gallery
.galleryOnly()
- .galleryMimeTypes( //no gif images at all
+ .galleryMimeTypes( // no gif images at all
mimeTypes = arrayOf(
"image/png",
"image/jpg",
@@ -84,7 +87,8 @@ class MainActivity : AppCompatActivity() {
)
)
// Image resolution will be less than 1080 x 1920
- .maxResultSize(1080, 1920)
+ // .maxResultSize(360, 420)
+ // .saveDir(getExternalFilesDir(null)!!)
.start(GALLERY_IMAGE_REQ_CODE)
}
@@ -93,10 +97,8 @@ class MainActivity : AppCompatActivity() {
// User can only capture image from Camera
.cameraOnly()
// Image size will be less than 1024 KB
- .compress(1024)
- .saveDir(Environment.getExternalStorageDirectory())
- // .saveDir(Environment.getExternalStorageDirectory().absolutePath+File.separator+"ImagePicker")
- // .saveDir(getExternalFilesDir(null)!!)
+ .compress(50)
+ .saveDir(getExternalFilesDir(null)!!)
.start(CAMERA_IMAGE_REQ_CODE)
}
@@ -104,20 +106,20 @@ class MainActivity : AppCompatActivity() {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
Log.e("TAG", "Path:${ImagePicker.getFilePath(data)}")
- // File object will not be null for RESULT_OK
- val file = ImagePicker.getFile(data)!!
+ // Uri object will not be null for RESULT_OK
+ val uri = data?.data!!
when (requestCode) {
PROFILE_IMAGE_REQ_CODE -> {
- mProfileFile = file
- imgProfile.setLocalImage(file, true)
+ mProfileUri = uri
+ imgProfile.setLocalImage(uri, true)
}
GALLERY_IMAGE_REQ_CODE -> {
- mGalleryFile = file
- imgGallery.setLocalImage(file)
+ mGalleryUri = uri
+ imgGallery.setLocalImage(uri)
}
CAMERA_IMAGE_REQ_CODE -> {
- mCameraFile = file
- imgCamera.setLocalImage(file, false)
+ mCameraUri = uri
+ imgCamera.setLocalImage(uri)
}
}
} else if (resultCode == ImagePicker.RESULT_ERROR) {
@@ -138,29 +140,29 @@ class MainActivity : AppCompatActivity() {
}
fun showImage(view: View) {
- val file = when (view) {
- imgProfile -> mProfileFile
- imgCamera -> mCameraFile
- imgGallery -> mGalleryFile
+ val uri = when (view) {
+ imgProfile -> mProfileUri
+ imgCamera -> mCameraUri
+ imgGallery -> mGalleryUri
else -> null
}
- file?.let {
- IntentUtil.showImage(this, file)
+ uri?.let {
+ startActivity(IntentUtils.getUriViewIntent(this, uri))
}
}
fun showImageInfo(view: View) {
- val file = when (view) {
- imgProfileInfo -> mProfileFile
- imgCameraInfo -> mCameraFile
- imgGalleryInfo -> mGalleryFile
+ val uri = when (view) {
+ imgProfileInfo -> mProfileUri
+ imgCameraInfo -> mCameraUri
+ imgGalleryInfo -> mGalleryUri
else -> null
}
AlertDialog.Builder(this)
.setTitle("Image Info")
- .setMessage(FileUtil.getFileInfo(file))
+ .setMessage(FileUtil.getFileInfo(this, uri))
.setPositiveButton("Ok", null)
.show()
}
diff --git a/sample/src/main/kotlin/com.github.dhaval2404.imagepicker/sample/util/FileUtil.kt b/sample/src/main/kotlin/com.github.dhaval2404.imagepicker/sample/util/FileUtil.kt
index 6b7416d2..7842b66d 100644
--- a/sample/src/main/kotlin/com.github.dhaval2404.imagepicker/sample/util/FileUtil.kt
+++ b/sample/src/main/kotlin/com.github.dhaval2404.imagepicker/sample/util/FileUtil.kt
@@ -1,6 +1,11 @@
package com.github.dhaval2404.imagepicker.sample.util
-import android.graphics.BitmapFactory
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import android.provider.OpenableColumns
+import com.github.dhaval2404.imagepicker.util.FileUriUtils
+import com.github.dhaval2404.imagepicker.util.FileUtil
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
@@ -15,17 +20,29 @@ import java.util.Locale
object FileUtil {
/**
- * @param file File
+ * @param context Context
+ * @param uri Uri
* @return Image Info
*/
- fun getFileInfo(file: File?): String {
- if (file == null || !file.exists()) {
+ fun getFileInfo(context: Context, uri: Uri?): String {
+ if (uri == null) {
return "Image not found"
}
- val resolution = getImageResolution(file)
+ // Get Resolution
+ val resolution = FileUtil.getImageResolution(context, uri)
+
+ // File Path
+ val filePath = FileUriUtils.getRealPath(context, uri)
+ val document = FileUtil.getDocumentFile(context, uri) ?: return "Image not found"
+
+ // Get Last Modified
val sdf = SimpleDateFormat("dd/MM/yyyy hh:mm:ss a", Locale.getDefault())
- val modified = sdf.format(file.lastModified())
+ val modified = sdf.format(document.lastModified())
+
+ // File Size
+ val fileSize = getFileSize(document.length())
+
return StringBuilder()
.append("Resolution: ")
@@ -37,16 +54,23 @@ object FileUtil {
.append("\n\n")
.append("File Size: ")
- .append(getFileSize(file))
+ .append(fileSize)
+ .append("\n\n")
+
+ .append("File Name: ")
+ .append(getFileName(context.contentResolver, uri))
.append("\n\n")
.append("File Path: ")
- .append(file.absolutePath)
+ .append(filePath)
+ .append("\n\n")
+
+ .append("Uri Path: ")
+ .append(uri.toString())
.toString()
}
- private fun getFileSize(file: File): String {
- val fileSize = file.length().toFloat()
+ private fun getFileSize(fileSize: Long): String {
val mb = fileSize / (1024 * 1024)
val kb = fileSize / (1024)
@@ -57,10 +81,29 @@ object FileUtil {
}
}
- private fun getImageResolution(file: File): Pair {
- val options = BitmapFactory.Options()
- options.inJustDecodeBounds = true
- BitmapFactory.decodeFile(file.absolutePath, options)
- return Pair(options.outWidth, options.outHeight)
+ fun getFileName(contentResolver: ContentResolver, uri: Uri): String? {
+ if (ContentResolver.SCHEME_FILE == uri.scheme) {
+ return File(uri.path).getName()
+ } else if (ContentResolver.SCHEME_CONTENT == uri.scheme) {
+ return getCursorContent(contentResolver, uri)
+ }
+ return null
+ }
+
+ private fun getCursorContent(
+ contentResolver: ContentResolver,
+ uri: Uri
+ ): String? {
+ return try {
+ val cursor = contentResolver.query(uri, null, null, null, null) ?: return null
+ var fileName: String? = null
+ if (cursor.moveToFirst()) {
+ fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+ }
+ cursor.close()
+ fileName
+ } catch (ex: Exception) {
+ null
+ }
}
}
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
index 65b5e343..1a996709 100644
--- a/sample/src/main/res/values/styles.xml
+++ b/sample/src/main/res/values/styles.xml
@@ -28,7 +28,7 @@
- 48dp
- 48dp
- centerInside
- - @color/grey_800
+ - @color/grey_800