How to save image to camera folder in Android Q?

10,612

Solution 1

The most generalized version I was able to write is:

private Uri saveImage(Context context, Bitmap bitmap, @NonNull String folderName, @NonNull String fileName) throws IOException {
    OutputStream fos = null;
    File imageFile = null;
    Uri imageUri = null;

    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ContentResolver resolver = context.getContentResolver();
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
            contentValues.put(
                    MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + folderName);
            imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

            if (imageUri == null)
                throw new IOException("Failed to create new MediaStore record.");

            fos = resolver.openOutputStream(imageUri);
        } else {
            File imagesDir = new File(Environment.getExternalStoragePublicDirectory(
                    Environment.DIRECTORY_PICTURES).toString() + File.separator + folderName);

            if (!imagesDir.exists())
                imagesDir.mkdir();

            imageFile = new File(imagesDir, fileName + ".png");
            fos = new FileOutputStream(imageFile);
        }


        if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos))
            throw new IOException("Failed to save bitmap.");
        fos.flush();
    } finally {
        if (fos != null)
            fos.close();
    }
    
    if (imageFile != null) {//pre Q
        MediaScannerConnection.scanFile(context, new String[]{imageFile.toString()}, null, null);
        imageUri = Uri.fromFile(imageFile);
    }
    return imageUri;
}

If you've found a better way, post here, I'll mark it as answer.

Solution 2

Can't we use media store for above and below Android Q? I have tried following way and it is working fine.

private fun writeImage() {
    val uri =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
        } else {
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        }

    val imageDetail = ContentValues().apply {
        put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "${System.currentTimeMillis()}.jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.IS_PENDING, 1)
        }
    }
    val contentUri = contentResolver.insert(uri, imageDetail)

    contentUri?.let {
        contentResolver.openFileDescriptor(contentUri, "w", null).use { pd ->
            pd?.let {
                /* val fos = FileOutputStream(it.fileDescriptor)
                   val array = getBitmapToBase64()
                   fos.write(array, 0, array.size)
                   fos.close() 
                */
                // or
                // Your logic to write an Image file.
            }
        }

        imageDetail.clear()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            imageDetail.put(MediaStore.Images.Media.IS_PENDING, 0)
            contentUri.let { contentResolver.update(it, imageDetail, null, null) }
        }

        // open the saved image file with gallery app
        Snackbar.make(
            findViewById(android.R.id.content), "saved", Snackbar.LENGTH_LONG
        ).setAction("Show") {
            val intent = Intent(Intent.ACTION_VIEW, contentUri)
            startActivity(intent)
        }.show()

    } ?: Toast.makeText(this, "not saved", Toast.LENGTH_LONG).show()
}

Solution 3

Using this document : https://developer.android.com/training/data-storage

Create temp file first

val mTempFileRandom = Random()

fun createTempFile(ext:String, context:Context):String {
  val path = File(context.getExternalCacheDir(), "AppFolderName")
  if (!path.exists() && !path.mkdirs())
  {
    path = context.getExternalCacheDir()
  }
  val result:File
  do
  {
    val value = Math.abs(mTempFileRandom.nextInt())
    result = File(path, "AppFolderName-" + value + "-" + ext)
  }
  while (result.exists())
  return result.getAbsolutePath()
}   

Send file from path

copyFileToDownloads(this@CameraNewActivity, File(savedUri.path))

Copy data to storage

MAIN_DIR: Main folder name in which you want to store image (Like App Name) IMAGE_DIR: Sub folder if you want to create.

    fun copyFileToDownloads(context: Context, downloadedFile: File): Uri? {

    // Create an image file name
    val timeStamp = SimpleDateFormat(DATE_FORMAT_SAVE_IMAGE).format(Date())
    val imageFileName = "JPEG_$timeStamp.jpg"
    val resolver = context.contentResolver

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val values = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, imageFileName)
            put(MediaStore.Images.Media.MIME_TYPE, IMAGE_MIME_TYPE)
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + File.separator + MAIN_DIR + File.separator + IMAGE_DIR + File.separator)
        }

        resolver.run {
            val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

            uri
        }
    } else {
        val authority = "${context.packageName}.provider"
        val imagePath =
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)?.absolutePath
        val destinyFile = File(imagePath, imageFileName)
        val uri = FileProvider.getUriForFile(context, authority, destinyFile)
        FileUtils.scanFile(context, destinyFile.absolutePath)

        uri
    }?.also { uri ->
        var writtenValue = 0L
        // Opening an outputstream with the Uri that we got
        resolver.openOutputStream(uri)?.use { outputStream ->
            downloadedFile.inputStream().use { inputStream ->
                writtenValue = inputStream.copyTo(outputStream)
                Log.d("Copy Written flag", " = $writtenValue")
            }
        }
    }
}

ScanFile : ( More detail : https://developer.android.com/reference/android/media/MediaScannerConnection )

fun scanFile(context:Context, path:String) {
  MediaScannerConnection.scanFile(context,
                                  arrayOf<String>(path), null,
                                  { newPath, uri-> if (BuildConfig.DEBUG)
                                   Log.e("TAG", "Finished scanning " + newPath) })
}
Share:
10,612
VolodymyrH
Author by

VolodymyrH

Hey! My name is Volodymyr, I'm Software Engineer who likes Android :)

Updated on July 08, 2022

Comments

  • VolodymyrH
    VolodymyrH almost 2 years

    I need to save an image to camera folder, but as Android Q getExternalStoragePublicDirectory is deprecated, I do it in another way. What I have (this method receive bitmap and its name):

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ContentResolver resolver = mContext.getContentResolver();
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/" + IMAGES_FOLDER_NAME);
            Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
            OutputStream fos = resolver.openOutputStream(imageUri);
            saved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
        } else {
            String imagesDir = Environment.getExternalStoragePublicDirectory(
                    Environment.DIRECTORY_DCIM).toString() + File.separator + IMAGES_FOLDER_NAME;
    
            File file = new File(imagesDir);
    
            if (!file.exists()) {
                file.mkdir();
            }
    
            File image = new File(
                    imagesDir,
                    name + ".png"
            );
    
            final long fileHashCode = image.hashCode();
            Logger.d(TAG, "saveImage, saving image file, hashCode = " + fileHashCode);
    
            FileOutputStream fos = new FileOutputStream(image);
            saved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
        }
    

    That perfectly works for all needed OS versions, but it looks inaccurate and I'd like to find a more common way. I tried to play around with content values or try some similar way as for Q, but it doesn't work. I've seen many questions here, but neither of them can help me.

    The question is how can I optimize saving for OS lower than Q?

  • Sagar gujarati
    Sagar gujarati over 4 years
    How can i display this image using Environment.getExternalStorageDirectory() +"DCIM/" + IMAGES_FOLDER_NAME bcz it's not working in android 10
  • Vikash Parajuli
    Vikash Parajuli about 4 years
  • RRGT19
    RRGT19 almost 4 years
    What is the IMAGES_FOLDER_NAME for? needs to be a custom folder name?
  • VolodymyrH
    VolodymyrH almost 4 years
    @RRGT19, optional folder name.
  • gpl
    gpl about 3 years
    best solution I have seen