Repository navigation
Conversation
getColumnIndexForNames picks the first column that exists, and its value was then read unconditionally. MediaStore rows always carry DATE_TAKEN, but it is NULL for anything that is not a photo or video, so a PDF's content:// uri threw "getString(...) must not be null". Size, last-modified and created are now read through getLongForNames, which takes the first listed column that exists and is not NULL for the row, using core-ktx's getStringOrNull. SIZE and COLUMN_LAST_MODIFIED are documented as null when unknown, so document providers are covered too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
In
IONFILEContentHelper, the size, last-modified and created values are now read through a newCursor.getLongForNameshelper. The helper returns the value of the first listed column that exists and is not NULL for the current row, so when a column is NULL it tries the next one. Before, the first column that merely existed was picked, and its value was read unconditionally.getCreatedTimestampForContentUri: whenDATE_TAKENis NULL, it falls back toDATE_ADDED.getLastModifiedTimestampForContentUri: behaves the same forDATE_MODIFIEDandCOLUMN_LAST_MODIFIED, then falls back to the created time as before. The photo-picker comment is kept.getSizeForContentUri: whenSIZEis NULL, it falls back toopenAssetFileDescriptor, which it already did when the column was missing.getNameForContentUristill usesgetColumnIndexForNames. When the name is NULL it keeps throwingUnknownError, as it does today.Context
On a MediaStore URI such as
content://media/external/file/18(a PDF in Downloads),getFileMetadatathrowsNullPointerException: getString(...) must not be null. The Capacitor plugin reports that asOS-PLUG-FILE-0013.MediaStore rows always have a
datetakencolumn, but it is NULL for anything that is not a photo or video.getColumnIndexForNameschoosesdatetakenbecause the column exists, and the Kotlin null check ongetStringthen throws. The row's_display_name,_sizeanddate_addedare all present, but they are lost with the exception.This hits any app that receives such a URI, for example through "Open with" from Huawei's Files app. It reproduces on a stock Android 16 emulator.
The problem is not limited to MediaStore. Android documents all three numeric columns as nullable:
OpenableColumns.SIZE: "Null if unknown."DocumentsContract.Document.COLUMN_SIZE: "nullif unknown."DocumentsContract.Document.COLUMN_LAST_MODIFIED: "nullif unknown."So a document provider for a remote file whose size or modified time it does not know throws in the same way. Android's own
dumpImageMetaDatasample callsisNullbefore it readsSIZE, for exactly this reason.Fixes ionic-team/capacitor-filesystem#88
Type of changes
Platforms affected
Tests
IONFILETestFileContentProvidergains aMediaStore.MediaColumns.DATE_TAKENcolumn, filled from a newTestFileContent.dateTaken: Long? = null. It defaults to NULL, which is what MediaStore does for non-media files.getFileMetadatasuccess tests fail atassertTrue(result.isSuccess). With the fix they pass again, still expectingTEST_TIMESTAMP(theDATE_ADDEDvalue).given file with date taken, when getting metadata from content uri, date taken is returned as timestampschecks that a non-NULLDATE_TAKENis still preferred overDATE_ADDED. Swapping the order of the two columns ingetCreatedTimestampForContentUrimakes this test fail../gradlew testDebugUnitTest: 137 tests, 0 failures. Run with JDK 21 and Robolectric 4.14.The helper reads with core-ktx's
getStringOrNull, which checksisNullbefore reading;androidx.core:core-ktxis already a dependency. A plaingetStringwould pass these tests too, becauseMatrixCursorreturns null for a NULL cell. But theCursordocs say whatgetStringdoes on a NULL is implementation-defined, so the helper does not rely on it.Checklist
RNMT-XXXX <title>(external contributor, no ticket to use)Follow-up, not in this PR: MediaStore's
DATE_ADDEDandDATE_MODIFIEDare in seconds, whileDATE_TAKENandCOLUMN_LAST_MODIFIEDare in milliseconds. Timestamps read from the first two are therefore 1000× too small. Fixing that changes returned values, so I would rather raise it separately.🤖 Generated with Claude Code