Angel Koh
Angel Koh

Reputation: 13525

MediaStore contentResolver.insert() creates copies instead of replacing the existing file when taking photos (Android Q: 29)

I am trying to save an image taken from the camera using the following codes:

@RequiresApi(Build.VERSION_CODES.Q)
private fun setImageUri(): Uri {
    val resolver = contentResolver
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "house2.jpg")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/OLArt")
    }

    imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

    return imageUri!!
}

The function works well for the first time. however when the image (house2.jpg) already exists, the system will create another file called "house2 (1).jpg", "house2 (2).jpg, etc (instead of replacing the old file)

enter image description here

is there anything I can set in the contentValues to force the resolver to replace the file rather than create copies of it?

below is the codes for the take picture intent.

 Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->

     takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, setImageUri()) //<- i paste in the imageUri here

     // Ensure that there's a camera activity to handle the intent
     takePictureIntent.resolveActivity(packageManager)?.also {

         startActivityForResult(takePictureIntent, 102)
     }
  }

Upvotes: 7

Views: 6081

Answers (4)

Saeed Arianmanesh
Saeed Arianmanesh

Reputation: 1428

Angel Koh's answer is correct.

I'm just posting it in Java:

@RequiresApi(Build.VERSION_CODES.Q)
public static Uri CheckIfUriExistOnPublicDirectory(Context c ,String[] projection, String selection){

    ContentResolver resolver = c.getContentResolver();
    Cursor cur = resolver.query(MediaStore.Downloads.EXTERNAL_CONTENT_URI, projection, selection , null, null);

    if (cur != null) {

        if(cur.getCount()>0){

            if (cur.moveToFirst()) {
                String filePath = cur.getString(0);
                long id = cur.getLong(cur.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
                String displayName = cur.getString(cur.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME) );
                String relativePath = cur.getString(cur.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH) );
                long z = cur.getLong(cur.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED) );

                return imageUri = ContentUris.withAppendedId(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,  id);

            } else {
                // Uri was ok but no entry found.
            }

        }else{
            // content Uri was invalid or some other error occurred
        }
        cur.close();
    } else {
        // content Uri was invalid or some other error occurred
    }

    return null;
}

and usage of method:

String[] projection = {MediaStore.MediaColumns._ID,
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.RELATIVE_PATH,
                MediaStore.MediaColumns.DATE_MODIFIED
        };

        String selection = MediaStore.MediaColumns.RELATIVE_PATH + "='" + Environment.DIRECTORY_DOWNLOADS + File.separator + folderName + File.separator + "' AND "
                + MediaStore.MediaColumns.DISPLAY_NAME+"='" + fileName + "'";

        uri = CheckIfUriExistOnPublicDirectory(context,projection,selection);
        if(uri != null){
            // file already exist
        }else{
            // file not exist, insert
        }

Upvotes: 4

Angel Koh
Angel Koh

Reputation: 13525

@CommonsWare's comment helped.

The idea is to

  1. Query if file already exists with resolver.query()
  2. If yes, extract the contentUri from the cursor
  3. Otherwise, use resolver.insert()

one thing to note when creating the selection for query is that MediaStore.MediaColumns.RELATIVE_PATH requires a terminating "/"

i.e. 'Pictures/OLArt/' << note the slash after OLArt/

    val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}='Pictures/OLArt/' AND " 
                   + "${MediaStore.MediaColumns.DISPLAY_NAME}='house2.jpg' "

The following is the updated codes.

@RequiresApi(Build.VERSION_CODES.Q)
private fun getExistingImageUriOrNullQ(): Uri? {
    val projection = arrayOf(
        MediaStore.MediaColumns._ID,
        MediaStore.MediaColumns.DISPLAY_NAME,   // unused (for verification use only)
        MediaStore.MediaColumns.RELATIVE_PATH,  // unused (for verification use only)
        MediaStore.MediaColumns.DATE_MODIFIED   //used to set signature for Glide
    )

    // take note of the / after OLArt
    val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}='Pictures/OLArt/' AND " 
                  + "${MediaStore.MediaColumns.DISPLAY_NAME}='house2.jpg' "

    contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        projection, selection, null, null ).use { c ->
        if (c != null && c.count >= 1) {

            print("has cursor result")
            c.moveToFirst().let {

                val id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID) )
                val displayName = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME) )
                val relativePath = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH) )
                lastModifiedDate = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED) )

                imageUri = ContentUris.withAppendedId(   
                             MediaStore.Images.Media.EXTERNAL_CONTENT_URI,  id)

                print("image uri update $displayName $relativePath $imageUri ($lastModifiedDate)")

                return imageUri
            }
        }
    }
    print("image not created yet")
    return null
}

I then add this method into my existing codes

@RequiresApi(Build.VERSION_CODES.Q)
private fun setImageUriQ(): Uri {

    val resolver = contentResolver

    imageUri = getExistingImageUriOrNullQ() //try to retrieve existing uri (if any)
    if (imageUri == null) {

       //=========================
       // existing codes for resolver.insert
       //(SNIPPED)
       //=========================
    }
    return imageUri!!
}

Upvotes: 6

Perraco
Perraco

Reputation: 17380

This is the correct expected behavior. The reason why you see different numeration postfixes, is because probably you are saving the files in the same folder, so Android has to create a unique name in order to allow the files to exist in the same location.

The Insert method is meant to create always new records. The Uri that it returns is always a newly inserted record. But if the file is saved in a folder where there is already another file with the same name, then as such file name must be different Android will append the numeric value.

If you wish to replace an existing record, then you must first locate its Uri, and then use it by calling instead the ContentResolver update method.

If you are saving photos from a camera app, then you could use instead the current time as the name, including the milliseconds, to ensure is unique.

Upvotes: 1

janavarro
janavarro

Reputation: 889

Have you tried using the method update?

Check if it creates a new one when there's nothing yet, if it doesn't work, then use insert or update depending if the file has been created.

Upvotes: -1

Related Questions