ionic-team / ionic-team/capacitor-filesystem

bug(android): stat fails with OS-PLUG-FILE-0013 on MediaStore content:// URIs whose datetaken is NULL (e.g. PDFs)

Open
#88 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
6
Forks
15
Avg merge
14m
Merged PRs (30d)
1

Description

## Capacitor Version

```
Installed Dependencies:
@capacitor/core: 8.5.0
@capacitor/android: 8.5.0
@capacitor/ios: 8.5.0
@capacitor/cli: 8.5.0
@capacitor/filesystem: 8.1.3 (io.ionic.libs:ionfilesystem-android 1.1.0)
```

## Platform(s)

Android. Reproduced on a stock Android 16 emulator (Google APIs image) and on a Huawei MatePad running Android 12.

## Current Behavior

`Filesystem.stat()` rejects with `OS-PLUG-FILE-0013` for a MediaStore `content://` URI that points at anything other than a photo or video, such as a PDF in Downloads. The native cause is a Kotlin null check:

```
java.lang.NullPointerException: getString(...) must not be null
```

The row itself is fine: it has `_display_name`, `_size` and `date_added`. The only thing missing is `datetaken`, which MediaStore leaves NULL for non-media files.

Some file managers hand these URIs to other apps. Huawei's Files app, for example, sends `content://media/external/file/` when you tap "Open with". As a result, `stat` fails for every PDF opened that way.

## Expected Behavior

`stat` resolves with the file's `name`, `size`, `mtime` and `ctime`. When `datetaken` is NULL, the created time falls back to `date_added`, which the library already lists as its second choice.

## Code Reproduction

1. Put any PDF in the device's Downloads folder, then look at its MediaStore row:
```sh
adb shell content query --uri content://media/external/file \
--projection _id:_display_name:datetaken:date_added --where "\"_display_name LIKE '%.pdf'\""
# Row: 0 _id=18, _display_name=Autumn Leaves.pdf, datetaken=NULL, date_added=1789320521
```
2. Receive that URI in a Capacitor app with a read grant. For example, add an `ACTION_VIEW` intent filter for `application/pdf`, open the file from a file manager, and pick the URL up from `App.addListener('appUrlOpen', …)`. Then call:
```ts
await Filesystem.stat({ path: 'content://media/external/file/18' });
// rejects: OS-PLUG-FILE-0013
```

## Other Technical Details

The bug is in `ion-android-filesystem`, in `IONFILEContentHelper.kt`:

- `getFileMetadata` queries with a null projection. It then reads the name, the size, the last-modified time and the created time from the same cursor.
- `getCreatedTimestampForContentUri` asks `getColumnIndexForNames(listOf(DATE_TAKEN, DATE_ADDED))` for a column. That function returns the first column that **exists**, and on a MediaStore cursor `datetaken` always exists, so it is always chosen.
- The code then calls `cursor.getString(columnIndex).toLongOrNull()`. Kotlin types `getString`'s platform return as non-null here, so the NULL `datetaken` throws before `toLongOrNull()` runs. The NPE is then mapped to `OS-PLUG-FILE-0013`.
- `getLastModifiedTimestampForContentUri` hits the same throw through its fallback. `getSizeForContentUri` has the same pattern for a NULL `_size`.
- This is not only a MediaStore problem. `OpenableColumns.SIZE`, `DocumentsContract.Document.COLUMN_SIZE` and `COLUMN_LAST_MODIFIED` are all documented as null when unknown. So a document provider serving a remote file can fail in the same way.

`main` and `1.1.1-beta.1` contain the same code.

The fix PR for `ion-android-filesystem` is ionic-team/ion-android-filesystem#12. It adds a small `Cursor.getLongForNames` helper that skips a column that is NULL for the current row. It also adds a `DATE_TAKEN` column to the test content provider, so the existing metadata tests cover the NULL case. Once that is released, this plugin needs a dependency bump.

## Additional Context

- **Workaround:** query `OpenableColumns.DISPLAY_NAME` directly from a small app-side plugin.
- **Earlier reports:** these have the same symptom but a different cause. Both come from before `stat` used `ion-android-filesystem`. In ionic-team/capacitor-plugins#308 (2020), `stat` did not support `content://` at all. In ionic-team/capacitor#6988 (2023), a Google Photos URI crashed `stat`, and the discussion was never answered. I found no report against the current implementation.
- **A second, separate issue:** MediaStore's `date_added` and `date_modified` are in seconds, but `datetaken` and `DocumentsContract`'s `last_modified` are in milliseconds. So, even with the fix, `ctime`/`mtime` for these rows come back 1000× too small. I have kept that out of the fix PR to keep it focused, and can open it separately.

Contributor guide

Open the contributing guide

Research direction

Start with IONFILEContentHelper.kt and ionic-team/ion-android-filesystem#12, then inspect the plugin's existing metadata tests and dependency declaration. After the upstream fix is released, update the dependency and run the metadata tests; done means stat handles content:// documents whose metadata columns are NULL.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin, typescript
Domain
mobile-dev
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.