anggrayudi / SimpleStorage

💾 Simplify Android Storage Access Framework for file management across API levels.
Apache License 2.0
805 stars 98 forks source link

Process: com.example.fileaccesssdk30, PID: 19939 android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE) #63

Closed HirenJodhani closed 2 years ago

HirenJodhani commented 3 years ago

Library version: 0.13.0 OS version: [Android 11] Device model: [EMUlater]

Describe the bug [when copy file to (document file(TreeFolder)) then file successfully copy(image/video) but when copyFileToDownloadMedia then cresh app when media is video if image then copyFileToDownloadMedia successfully.]

My Code

FileDescription fileDescription = new FileDescription(srcFile.getName(), "", srcFile.getType());
DocumentFileUtils.copyFileToDownloadMedia(srcFile, getApplicationContext(), fileDescription, new FileCallback() {

Stacktrace

E/AndroidRuntime: FATAL EXCEPTION: Thread-3
    Process: com.example.fileaccesssdk30, PID: 19939
    android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:178)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
        at android.content.ContentProviderProxy.insert(ContentProviderNative.java:549)
        at android.content.ContentResolver.insert(ContentResolver.java:2149)
        at android.content.ContentResolver.insert(ContentResolver.java:2111)
        at com.anggrayudi.storage.media.MediaStoreCompat.createMedia(MediaStoreCompat.kt:150)
        at com.anggrayudi.storage.media.MediaStoreCompat.createDownload(MediaStoreCompat.kt:33)
        at com.anggrayudi.storage.file.DocumentFileUtils.copyFileToMedia(DocumentFileExt.kt:2139)
        at com.anggrayudi.storage.file.DocumentFileUtils.copyFileToDownloadMedia(DocumentFileExt.kt:2154)
        at com.anggrayudi.storage.file.DocumentFileUtils.copyFileToDownloadMedia$default(DocumentFileExt.kt:2153)
        at com.anggrayudi.storage.file.DocumentFileUtils.copyFileToDownloadMedia(Unknown Source:28)
        at com.example.fileaccesssdk30.SimpleStorageSdk30CopyFolder$1$1.run(SimpleStorageSdk30CopyFolder.java:69)
        at java.lang.Thread.run(Thread.java:923)
eclectice commented 3 years ago

If I pass _MediaStore.VOLUMEEXTERNAL as the volume name in the MediaStore.Files.getContentUri() method on a non-Android10+ device with API29+ as the target SDK version, I get an error report that looks almost identical to yours, but with different lines and an exception statement.

Uri uri = getContext().getContentResolver().insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), contentValues);
 FATAL EXCEPTION: main
 java.lang.IllegalArgumentException: no path was provided when inserting new file
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:165)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
    at android.content.ContentProviderProxy.insert(ContentProviderNative.java:476)
    at android.content.ContentResolver.insert(ContentResolver.java:1280)

The _MediaStore.VOLUMEEXTERNAL actually stores the "external" string. The method generates the content://media/<volume_name>/file string. Hence, it becomes content://media/external/file.

Only Android10+ devices can use _MediaStore.VOLUMEEXTERNAL and _MediaStore.VOLUME_EXTERNALPRIMARY. If you use a device that isn't Android 10+, you can expect it to fail!

Given that your device is running Android 10+, there must be something else wrong.

Your issue occurs at line 150, as follows:

else -> MediaFile(context, context.contentResolver.insert(mediaType.writeUri!!, contentValues) ?: return null)

where mediaType.writeUri in the above code contains MediaStore.Downloads.getContentUri(MediaStoreCompat.volumeName), which depends on the Android version of the device; the volume name can be either _MediaStore.VOLUMEEXTERNAL and _MediaStore.VOLUME_EXTERNALPRIMARY. The MediaStore.Downloads.getContentUri() method generates the content://media/<volume_name>/downloads string.

As I previously stated, both, _MediaStore.VOLUMEEXTERNAL and _MediaStore.VOLUME_EXTERNALPRIMARY, are not supported on Android Pie (9.0) and below devices; even the MediaStore.Downloads class as well. As a result, in this case, it will never reach line 150 and will instead jump to line 152.

The code that holds the MediaStoreCompat.volumeName value is strange and pointless for the first case if it's running on non-Android 10+ devices.

    @JvmStatic
    val volumeName: String
        @SuppressLint("InlinedApi")
        get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) MediaStore.VOLUME_EXTERNAL else MediaStore.VOLUME_EXTERNAL_PRIMARY

On Android 10+ devices, it will go for the second case that selects _MediaStore.VOLUME_EXTERNALPRIMARY. It refers to a specific volume name that represents the primary external storage device at Environment.getExternalStorageDirectory(). It actually stores the "external_primary" string. Hence, MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) method returns content://media/external_primary/downloads. On some devices, the volume may not be found.

Instead of simply two lines of code, you may need to supply a considerable code extract as well as the actual location of the source file before calling DocumentFileUtils.copyFileToDownloadMedia() to better comprehend your problem.

Nonetheless, I came across this StackOverflow entry that is comparable to your problem, where @CommonsWare stated:

"You are calling insert() for a path that you have already insert()-ed before."

anggrayudi commented 2 years ago

@eclectice explained very well. Thank you.

In your case @HirenJodhani, you're trying to create a new file which actually already exists in the current folder.