Skip to content

Instantly share code, notes, and snippets.

@ZanderZhan
Last active January 10, 2022 09:33
Show Gist options
  • Save ZanderZhan/d2b00cb5b186fc9f8544ea23af83acfb to your computer and use it in GitHub Desktop.
Save ZanderZhan/d2b00cb5b186fc9f8544ea23af83acfb to your computer and use it in GitHub Desktop.
ImageWorker
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import java.io.File
import java.io.FileOutputStream
object ImageUtil {
fun canReadToExternal(context: Context): Boolean {
return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
&& context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
&& Environment.isExternalStorageEmulated()
}
fun isExternalStorage(context: Context, path: String): Boolean {
return path.isNotEmpty()
&& (path.startsWith(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)?.absolutePath
?: "", true
)
|| path.startsWith(Environment.getExternalStorageDirectory().absolutePath, true))
}
fun requestPermission(
fragment: Fragment,
callback: ActivityResultCallback<Map<String, Boolean>>
) {
fragment.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
callback
).launch(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
)
}
fun notify(
context: Context,
paths: Array<String>,
callback: MediaScannerConnection.OnScanCompletedListener
) {
MediaScannerConnection.scanFile(
context,
paths,
null,
callback
)
}
/*
* share image to other app.
* use fileProvider in Android N and up,
* so note that write fileprovider in your AndroidManifest.xml and declare external-cache-path in the filepaths
* */
fun shareImageToSystem(context: Context, bitmap: Bitmap): Boolean {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "image/*"
try {
val file =
File(context.externalCacheDir, "${System.currentTimeMillis()}.jpg")
val out = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
out.close()
val bmpUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val uri =
FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
intent.setDataAndType(uri, "image/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
uri
} else {
Uri.fromFile(file)
}
intent.putExtra(Intent.EXTRA_STREAM, bmpUri)
val chooser =
Intent.createChooser(intent, "Share Image")
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(chooser)
}
} catch (e: Throwable) {
return false
}
return true
}
}
import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import androidx.activity.result.ActivityResultCallback
import androidx.annotation.IntRange
import java.io.File
import java.io.FileOutputStream
/* source: https://gist.github.com/ZanderZhan/d2b00cb5b186fc9f8544ea23af83acfb
* If you want to save your image to gallery, please note that (for android 9 and less)
* 1. add permission WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE in AndroidManifest.xml
* 2. use ImageWorker#requestPermission() to request permission in your code
* For example:
ImageWorker.with(context)
.name("image_${System.currentTimeMillis()}")
.requestPermission {
ImageUtil.requestPermission(fragment, it)
}
.onPermissionDeny {
Toast.makeText(context, "permission deny", Toast.LENGTH_SHORT)
}
.onSucc { Toast.makeText(context, "save successful", Toast.LENGTH_SHORT) }
.onFail { Toast.makeText(context, "save error", Toast.LENGTH_SHORT) }
.write(bitmap)
*
* */
class SaveOption(
var directory: String,
var name: String,
var quality: Int,
var format: Bitmap.CompressFormat
)
class CallBackAction(
var permissionRequest: ((callback: ActivityResultCallback<Map<String, Boolean>>) -> Unit)? = null,
var permissionDeny: () -> Unit,
var onSuccess: () -> Unit,
var onFail: () -> Unit
)
fun Bitmap.CompressFormat.toType(): String {
return when (this) {
Bitmap.CompressFormat.JPEG -> "jpeg"
Bitmap.CompressFormat.PNG -> "png"
else -> "webp"
}
}
class ImageWorker private constructor(val context: Context) {
private val defaultDirectory =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) "" else Environment.getExternalStorageDirectory().absolutePath + File.separator + context.packageName
private val saveOption = SaveOption(defaultDirectory, "", 100, Bitmap.CompressFormat.JPEG)
private val callbackAction = CallBackAction(null, {}, {}, {})
companion object {
fun with(context: Context): ImageWorker {
return ImageWorker(context)
}
}
// default to gallery if empty
fun directory(path: String): ImageWorker {
saveOption.directory = if (path.isEmpty()) defaultDirectory else path
return this
}
fun name(name: String): ImageWorker {
saveOption.name = name
return this
}
fun format(format: Bitmap.CompressFormat): ImageWorker {
saveOption.format = format
return this
}
fun quality(@IntRange(from = 0, to = 100) quality: Int): ImageWorker {
saveOption.quality = quality
return this
}
fun requestPermission(permissionRequest: (callback: ActivityResultCallback<Map<String, Boolean>>) -> Unit): ImageWorker {
callbackAction.permissionRequest = permissionRequest
return this
}
fun onPermissionDeny(permissionDeny: () -> Unit): ImageWorker {
callbackAction.permissionDeny = permissionDeny
return this
}
fun onSucc(succ: () -> Unit): ImageWorker {
callbackAction.onSuccess = succ
return this
}
fun onFail(fail: () -> Unit): ImageWorker {
callbackAction.onFail = fail
return this
}
@Throws(IllegalArgumentException::class)
private fun checkFileName() {
if (saveOption.name.isEmpty()) {
throw IllegalArgumentException("file name cannot be empty")
}
}
@Throws(SecurityException::class, IllegalArgumentException::class)
fun write(bitmap: Bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& isSaveToGallery()
) {
val saveResult = insertToGallery(bitmap)
if (saveResult) {
callbackAction.onSuccess.invoke()
} else {
callbackAction.onFail.invoke()
}
} else {
insertToInternal(bitmap, true)
}
}
@Throws(SecurityException::class, IllegalArgumentException::class)
fun insertToInternal(bitmap: Bitmap, invokeAction: Boolean) {
checkFileName()
if (isSaveToGallery() && !ImageUtil.canReadToExternal(context)) {
if (callbackAction.permissionRequest != null) {
// remember to request permission in your AndroidManifest.xml
callbackAction.permissionRequest?.invoke { grantResults ->
if (grantResults.isNotEmpty() &&
grantResults.all { it.value }
) {
val saveResult = save(bitmap)
if (invokeAction) {
if (saveResult) {
callbackAction.onSuccess.invoke()
} else {
callbackAction.onFail.invoke()
}
}
} else {
callbackAction.permissionDeny.invoke()
}
}
} else {
// request permission first
throw SecurityException("you must request WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE permission in your AndroidManifest.xml and you code")
}
} else {
val saveResult = save(bitmap)
if (invokeAction) {
if (saveResult) {
callbackAction.onSuccess.invoke()
} else {
callbackAction.onFail.invoke()
}
}
}
}
private fun save(bitmap: Bitmap): Boolean {
val fileDirectory = File(saveOption.directory)
if (!fileDirectory.isDirectory || !fileDirectory.exists()) {
fileDirectory.mkdirs()
}
val file = File(getSaveFilePath())
val out = FileOutputStream(file)
val result = bitmap.compress(saveOption.format, saveOption.quality, out)
out.flush()
out.close()
ImageUtil.notify(context, arrayOf(file.absolutePath)) { path: String, uri: Uri ->
Log.i(this@ImageWorker.javaClass.simpleName, "Finish Scan File: $uri")
}
return result
}
/*
* use scoped storage >= android 10
* */
@Throws(SecurityException::class, IllegalArgumentException::class)
fun insertToGallery(bitmap: Bitmap): Boolean {
checkFileName()
val contentValues = ContentValues()
contentValues.put(MediaStore.Images.Media.DESCRIPTION, "image")
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, saveOption.name)
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/${saveOption.format.toType()}")
val contentUri = context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
if (contentUri != null) {
context.contentResolver.openFileDescriptor(contentUri, "w", null)
.use { fileDescriptor ->
fileDescriptor?.let {
val out = FileOutputStream(it.fileDescriptor)
bitmap.compress(saveOption.format, saveOption.quality, out)
out.close()
it.close()
return true
}
}
}
return false
}
private fun getSaveFilePath(): String {
return saveOption.directory + File.separator + saveOption.name + "." + saveOption.format.toType()
}
private fun isSaveToGallery(): Boolean {
return saveOption.directory.isEmpty() ||
ImageUtil.isExternalStorage(context, saveOption.directory)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment