Skip to content

Commit 5131ec9

Browse files
MozLandocsadilek
andcommitted
6393: Closes mozilla-mobile#6317: Prevent inserting duplicate record into content resolver r=Amejia481 a=csadilek OK, this hole was deep :). The cause of this crash is that we were unconditionally inserting into the content resolver which may already have a row / record of the download URI: mozilla-mobile#6317 (comment) This can happen if a download fails or gets cancelled before we write the file. We will have a unique file name generated based on existing files, but also need to check if we have a record of the file in the resolver. If so, use it, otherwise create a new record. I've tried for a few hours to write a meaningful test for this, but there are simply too many static methods involved here and the resulting refactoring was terrible: `ContentUris.withAppendedId`, `MediaStore.setIncludePending`, `MediaStore.Downloads.getContentUri` etc. That's properly the reason we don't have an existing test for this method :( This also makes sure we now won't crash if for some reason there's another problem inserting into the content resolver, but fail the download instead. Co-authored-by: Christian Sadilek <[email protected]>
2 parents bda5b65 + 5597c3c commit 5131ec9

File tree

1 file changed

+33
-6
lines changed

1 file changed

+33
-6
lines changed

components/feature/downloads/src/main/java/mozilla/components/feature/downloads/AbstractFetchDownloadService.kt

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import android.app.DownloadManager.EXTRA_DOWNLOAD_ID
1010
import android.app.Service
1111
import android.content.ActivityNotFoundException
1212
import android.content.BroadcastReceiver
13+
import android.content.ContentUris
1314
import android.content.ContentValues
1415
import android.content.Context
1516
import android.content.Intent
1617
import android.content.Intent.ACTION_VIEW
1718
import android.content.IntentFilter
19+
import android.net.Uri
1820
import android.os.Build
1921
import android.os.Build.VERSION.SDK_INT
2022
import android.os.Environment
@@ -441,14 +443,39 @@ abstract class AbstractFetchDownloadService : Service() {
441443

442444
val resolver = applicationContext.contentResolver
443445
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
444-
val item = resolver.insert(collection, values)
445446

446-
val pfd = resolver.openFileDescriptor(item!!, "w")
447-
ParcelFileDescriptor.AutoCloseOutputStream(pfd).use(block)
447+
// Query if we have a pending download with the same name. This can happen
448+
// if a download was interrupted, failed or cancelled before the file was
449+
// written to disk. Our logic above will have generated a unique file name
450+
// based on existing files on the device, but we might already have a row
451+
// for the download in the content resolver.
452+
var downloadUri: Uri? = null
453+
resolver.query(
454+
MediaStore.setIncludePending(collection),
455+
arrayOf(MediaStore.Downloads._ID),
456+
"${MediaStore.Downloads.DISPLAY_NAME} = ?",
457+
arrayOf("${download.fileName}"),
458+
null
459+
)?.use {
460+
if (it.count > 0) {
461+
val idColumnIndex = it.getColumnIndex(MediaStore.Downloads._ID)
462+
it.moveToFirst()
463+
downloadUri = ContentUris.withAppendedId(collection, it.getLong(idColumnIndex))
464+
}
465+
}
466+
467+
if (downloadUri == null) {
468+
downloadUri = resolver.insert(collection, values)
469+
}
470+
471+
downloadUri?.let {
472+
val pfd = resolver.openFileDescriptor(it, "w")
473+
ParcelFileDescriptor.AutoCloseOutputStream(pfd).use(block)
448474

449-
values.clear()
450-
values.put(MediaStore.Downloads.IS_PENDING, 0)
451-
resolver.update(item, values, null, null)
475+
values.clear()
476+
values.put(MediaStore.Downloads.IS_PENDING, 0)
477+
resolver.update(it, values, null, null)
478+
} ?: throw IOException("Failed to register download with content resolver")
452479
}
453480

454481
@TargetApi(Build.VERSION_CODES.P)

0 commit comments

Comments
 (0)