I am working with Android version 10.
I have enabled Permissions to Read & Write Storage
Device Name : Poco F1
Scenario: I have to capture a screenshot of the current layout and save it to internalStorage and preview that image to the user. Here users have an option to delete the image.
Here are the codes I am using to save & delete
Saving a screenshot:
//I will pass the bitmap here
fun saveBitmapToInternalStorage(bitmap: Bitmap?) {
bitmap?.let {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
saveBitmapToOlderDevice(it)
} else {
saveBitmapToNewerDevice(it)
}
}
}
//This method is to save image to newerdevice >= Q
#RequiresApi(Build.VERSION_CODES.Q)
private fun saveBitmapToNewerDevice(bitmap: Bitmap) {
val uri = generateUri()
context.contentResolver.openOutputStream(uri ?: return).use { outputStream ->
outputStream?.let {
writeBitmapToJpeg(bitmap, outputStream, uri.toString())
}
}
}
//This is to generate the URI.
#RequiresApi(Build.VERSION_CODES.Q)
private fun generateUri(): Uri? {
val dateFormat = getDateFormat()
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "${dateFormat}.jpg")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/${context.resources.getString(R.string.app_name)}")
}
return context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
}
// To save images to olderDevice
private fun saveBitmapToOlderDevice(bmp: Bitmap) {
val filename = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)?.absolutePath +
"/${context.resources.getString(R.string.app_name)}/${getDateFormat()}.jpg"
createDirectory(filename)
val outputStream = FileOutputStream(filename)
writeBitmapToJpeg(bmp, outputStream, filename)
}
//This method is to save the image to InternalStorage
private fun writeBitmapToJpeg(bmp: Bitmap, outputStream: OutputStream, imagePath: String) {
try {
val outputData = ByteArrayOutputStream()
bmp.compress(CompressFormat.JPEG, 100, outputData)
outputData.writeTo(outputStream)
outputStream.flush()
outputStream.close()
} catch (e: IOException) {
showBitmapWriteErrorMessage()
}
}
I save the path while storing the image in internalStorgae
the path looks like
/storage/emulated/0/Pictures/TGP AR/20211011142001.jpg
and i pass this path into below method
To delete the image :
private fun deleteImage(imagePath: String) {
val file = File(imagePath)
file.delete()
}
file.exists() is returning true.
file.delete() is returning false.
I think, there might be two different ways to delete ( > & < Q ).
Please help me
You can delete the image by modifying your method to the following:
private fun deleteImage(imagePath: Uri) {
getContentResolver().delete(imagePath, null, null)
}
Then pass the Uri created in generateUri() to delete the file.
Related
I save a png image to external storage using this block of code for sdk<=28
/**
* save image with this method if the sdk is 28 or lower
*/
private fun saveImageSdk28(fileName: String){
//declar the output stream variable outside of try/catch so that it can always be closed
var imageOutputStream: FileOutputStream? = null
var outputImageFile = getFile(fileName)
if (!outputImageFile.exists()) {
outputImageFile.createNewFile()
}
try {
imageOutputStream = FileOutputStream(outputImageFile)
encryptedBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutputStream)
} catch (e: IOException) {
e.printStackTrace()
Timber.i(e.toString())
} finally {
if (imageOutputStream != null) {
imageOutputStream.flush()
imageOutputStream.close()
}
}
}
/**
* returns file from fileName
*/
fun getFile(fileName: String): File{
//open, or create the directory where the image will be stored
var directory = File(
Environment.getExternalStorageDirectory().toString() + "/AppNameOutput/"
)
if (!directory.exists()) {
directory.mkdir()
}
//create the file
var file: File = File(directory.absolutePath, fileName)
return file
}
and this code for when the sdk>28
/**
* save image with this method if the sdk is 29 or higher
*/
#RequiresApi(Build.VERSION_CODES.Q)
private fun saveImageSdk29(fileName: String){
val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$fileName")
put(MediaStore.Images.Media.MIME_TYPE, "image/png")
put(MediaStore.Images.Media.WIDTH, encryptedBitmap.width)
put(MediaStore.Images.Media.HEIGHT, encryptedBitmap.height)
}
try{
val contentResolver = getApplication<Application>().contentResolver
contentResolver.insert(imageCollection, contentValues)?.also {uri->
contentResolver.openOutputStream(uri).use {outputStream ->
encryptedBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
}
}
}catch (e: IOException){
e.printStackTrace()
}
}
The image sucsessfully saves on the users device and can be accesed through files, however, the user can't access these images through the gallery, or Images tab.
I solved it. Turns out you just need to wait a while and reboot the phone for the gallery to show your images.
This Code Works Fine With Media Files I want a solution For Document Files
I Don't Know how to put contentValues For Document Files
fun getFile(fileName: String): File? {
with(sharePrefHelper.app){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues()
// Here is My Question That what should i Do Here Because this is for document not for image
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
// for MIME_TYPE "image/jpg" this is working
values.put(MediaStore.Images.Media.MIME_TYPE, "text/csv")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Donny")
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.let {
it.path?.let { finalPath ->
return File(finalPath)
}
}
} else {
val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Donny")
if (!directory.exists()){
directory.mkdirs()
}
return File(directory, fileName)
}
return null
}
}
This Code Works Fine with media Files
My Question Here is How to save documents like CSV File in outer folder of android device
EDIT :
Well well well, I'm still trying to add anytype of file in the "download" directory.
Personnaly, I'm trying to copy a file from my assetFolder and paste it to the "Download" folder. I haven't succeeded yet.
However, I can currently CREATE anytype of file in that folder, it's working with the method below. I hope this can help you.
Here is my code :
public void saveFileToPhone(InputStream inputStream, String filename) {
OutputStream outputStream;
Context myContext = requireContext();
try {
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.Q){
ContentResolver contentResolver = requireContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Downloads.DISPLAY_NAME,filename);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri fileUri = contentResolver.insert(collection, contentValues);
outputStream = contentResolver.openOutputStream(Objects.requireNonNull(fileUri));
Objects.requireNonNull(outputStream);
}
}catch (FileNotFoundException e) {
e.printStackTrace();
}
}
Here it is what i have done with clean way
This is file provider activity
class FileProviderActivity : AppCompatActivity() {
var commonIntentLauncher: ActivityResultLauncher<Intent?> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.let {
intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
"FileUri" to result?.data?.data.toString()
))
finish()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val activityIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = intent.getStringExtra("fileType")
putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("fileName"))
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
}
commonIntentLauncher.launch(activityIntent)
}else{
val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/Dunny/CSVFiles")
if (!directory.exists()){
directory.mkdirs()
}
intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
"FileUri" to File(directory, intent.getStringExtra("fileName")!!).toURI().toString()
))
finish()
}
}
}
This FileProviderHelper
class MyFileProvider {
companion object {
fun with(context: Context) = FileRequest(context)
}
class FileRequest(private val context: Context) {
fun request(fileName: String, fileType: String = "application/*", file: (Uri?) -> Unit ) {
val intent = Intent(context, FileProviderActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("fileName",fileName)
.putExtra("fileType", fileType)
.putExtras(bundleOf("FileReceiver" to FileReceiver(file)))
context.startActivity(intent)
}
}
internal class FileReceiver(private val file: (Uri?) -> Unit) : ResultReceiver(Handler(Looper.getMainLooper())) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
super.onReceiveResult(resultCode, resultData)
resultData?.let {
file(it.getString("FileUri")?.toUri())
}
}
}
}
Here Is Use Of this Function
MyFileProvider.with(this).request("TestFile.csv","application/*") { fileUri ->
toast(fileUri.toString())
}
I want to save a file (Image, Audio, Video, Document) from the internal storage of my application to the Public directories depending on the File type. So i made this function
#RequiresApi(Build.VERSION_CODES.Q)
private fun saveFileUsingMediaStore(file: File, mimeType: String, fileName: String, destinationDirectory: String) {
var uri: Uri? = null
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
put(MediaStore.MediaColumns.RELATIVE_PATH, destinationDirectory)
}
runCatching {
with(appContext.contentResolver) {
insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)?.let {
uri = it
file.inputStream().use { input ->
openOutputStream(it)?.use { output ->
input.copyTo(output, DEFAULT_BUFFER_SIZE)
} ?: throw IOException("Failed to open output stream.")
}
} ?: {
toast("Failed to create MediaStore record")
//throw IOException("Failed to create new MediaStore record.")
}
}
}.getOrElse {
uri?.let { failedUri ->
toast("Delete orphan entry")
appContext.contentResolver.delete(failedUri, null, null)
}
}
}
How ever, when the code reach to the insert method, the code stops and does not do anything. Its like the insert never happens. Is there something wrong?
The problem solved when i changed the MediaStore.Downloads.EXTERNAL_CONTENT_URI with MediaStore.*.getContentUri("external")
The * gets its value by the fileType of the file that i want to save.
val externalUri = when (fileType) {
IMAGE_TYPE -> MediaStore.Images.Media.getContentUri("external")
AUDIO_TYPE -> MediaStore.Audio.Media.getContentUri("external")
VIDEO_TYPE -> MediaStore.Files.getContentUri("external")
DOCUMENT_TYPE -> MediaStore.Files.getContentUri("external")
else -> MediaStore.Files.getContentUri("external")
}
So now the code that works is the following
with(appContext.contentResolver) {
insert(externalUri, contentValues)?.let {
.....
However, i dont know the real reason that caused the error
I am making a project that generates qr code. When user generates a qr code, A download button appears on the screen. If the user clicks this button. Application should save the image(qr code) to external storage. I only want to create a function to save the qr code. But i could not find any useful source. How can i save a bitmap to external storage?
use this function and pass bitmap on button click
from fragment
saveBitmapInStorage(bitmap!!, requireContext())
from activity
saveBitmapInStorage(bitmap!!, this)
main function
open fun saveBitmapInStorage(bitmap: Bitmap, context: Context) {
val filename = "QR_"+"${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.contentResolver?.also { resolver ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
} else {
val imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
}
fos?.use {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
}
}
why I share because I'm using this code in my current project and its working for me
hope helpful for you.
I am trying to take a screenshot of the displayed activity.
In this case, the activity contains a webview (wvMainView).
The problem is, the main content of the screen (usually a chart), does not appear in the screenshot. The only time I get the full screenshot is when I have a table inside the webpage.
Here is the code for the screenshot:
var lMainActivityLayout: ConstraintLayout? = findViewById(R.id.lMainActivityLayout)
val bitmap = getScreenShotFromView(lMainActivityLayout!!)
// val bitmap = getScreenShotFromView(wvMainView!!)
if (bitmap != null){ saveMediaToStorage(bitmap) }
private fun getScreenShotFromView(v: View): Bitmap?
{
Log.i("-","MainActivity > getScreenShotFromView")
var screenshot: Bitmap? = null
try
{
screenshot = Bitmap.createBitmap(v.measuredWidth, v.measuredHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(screenshot)
v.draw(canvas)
}
catch (e: Exception)
{
Log.e("GFG", "Failed to capture screenshot because:" + e.message)
}
return screenshot
}
private fun saveMediaToStorage(bitmap: Bitmap)
{
Log.i("-","MainActivity > saveMediaToStorage")
val filename = "${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
this.contentResolver?.also { resolver ->
val contentValues = ContentValues().apply
{
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
}
else
{
val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
}
fos?.use
{
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
Toast.makeText(this , "Image saved to Gallery!" , Toast.LENGTH_SHORT).show()
}
}
As for the screenshot, take a look at the example below. When I run the app and take the screenshot, inside the gray area there is a bar chart that simple won't show up in the screenshot.
I tried taking a screenshot of the main layout as well as of the web view but with the same result.
The iOS version of the app works fine.
Any idea on what causes this strange behavior?
Maybe I should take the screenshot of the entire screen and not of a certain view (is this possible)?
And another small issue - the screenshot does no always appear in the Gallery app although I can find it using the Files app.
I ended up using ScreenShotty for this - https://github.com/bolteu/screenshotty
Add this to build.graddle:
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
Here's the code, maybe it helps someone:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
{
Log.i("","MainActivity > onActivityResult")
super.onActivityResult(requestCode, resultCode, data)
screenshotManager!!.onActivityResult(requestCode, resultCode, data)
}
fun getFullScreenshot()
{
Log.i("","MainActivity > getFullScreenshot")
val screenshotResult = screenshotManager!!.makeScreenshot()
val subscription = screenshotResult.observe(
onSuccess =
{
// Add a delay to prefent lag / crash on Android 5.0/5.1.
// Not sure if this is the correct way but it works for me
Handler(Looper.getMainLooper()).postDelayed({ editScreenshot(it) }, 1000)
},
onError =
{
Log.i("", "Screenshot failed!")
}
)
}
fun editScreenshot(screenshot: Screenshot)
{
Log.i("","MainActivity > editScreenshot")
val width: Int = Resources.getSystem().getDisplayMetrics().widthPixels
val height: Int = Resources.getSystem().getDisplayMetrics().heightPixels
val bitmap = when (screenshot)
{
is ScreenshotBitmap -> screenshot.bitmap
}
// Multiple resolutions cases go here
bitmap?.apply {
cropRectangle(
xOffset = 50,
yOffset = 250,
newWidth = width - 100,
newHeight = height - 450
)?.let { saveMediaToStorage(it) }
}
}
fun saveMediaToStorage(bitmap: Bitmap)
{
Log.i("","MainActivity > saveMediaToStorage")
val screenshotFileName = "${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
this.contentResolver?.also { resolver ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, screenshotFileName)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
}
else
{
val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, screenshotFileName)
fos = FileOutputStream(image)
}
fos?.use {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
Toast.makeText(this , "Captured View and saved to Gallery" , Toast.LENGTH_SHORT).show()
}
}