Last active
January 10, 2022 09:33
-
-
Save ZanderZhan/d2b00cb5b186fc9f8544ea23af83acfb to your computer and use it in GitHub Desktop.
ImageWorker
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.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 | |
} | |
} |
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.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([email protected], "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