Fixed
Status Update
Comments
vi...@google.com <vi...@google.com>
vi...@google.com <vi...@google.com> #2
We’ve shared this with our product and engineering teams and will continue to provide updates as more information becomes available.
vi...@google.com <vi...@google.com> #3
Thank you very much for filing this issue with so much detail and clarity!
We cannot make any changes to Android 10 and Android 11 anymore, so the behavior cannot change on these versions. We recommend using mode "rwt" as a workaround.
We will update the public docs with this information and we are reconsidering changes for the long term behavior of these APIs.
We cannot make any changes to Android 10 and Android 11 anymore, so the behavior cannot change on these versions. We recommend using mode "rwt" as a workaround.
We will update the public docs with this information and we are reconsidering changes for the long term behavior of these APIs.
an...@steadfastinnovation.com <an...@steadfastinnovation.com> #4
Thank you for the update and for the recommended workaround!
Description
There's an undocumented (as far as I can tell) behavior change in Android 10 when using mode .
"w"
withParcelFileDescriptor.parseMode
Android 9 and below
Using mode
"w"
and writing to a file that already exists truncates the file, completely overwriting it.Android 10 and above
Using mode
"w"
and writing to a file that already exists no longer truncates the file being overwritten. This can result in a corrupt file if the new file is smaller than the old file.Workaround for
ParcelFileDescriptor.parseMode
In order to get the old behavior on Android 10+, the user simply needs to use mode , however, their meanings are not documented there.
"wt"
. This mode, and the other available modes, are listed inParcelFileDescriptor.parseMode
Fallout:
ContentResolver.open*
The real issue resides in APIs that use
ParcelFileDescriptor.parseMode
under-the-hood, such as the variousContentResolver.open*
APIs (in particular when using the SAF and file system URIs). There are two main issues:ContentResolver.openOutputStream
"w"
by default. So anyone using this API unknowingly gets this behavior change on Android 10+.The various
ContentResolver.open*
APIs either:a. Do not list any acceptable modes in their documentation.
b. List only a subset of modes that lists.
ParcelFileDescriptor.parseMode
c. Refer to various
ContentProvider.open*
APIs, which also do (a) and (b) above.d. None of the ). Using mode
ContentResolver.open*
orContentProvider.open*
APIs list mode"wt"
as an acceptable mode (despite it being listed byParcelFileDescriptor.parseMode
"wt"
with these APIs results in other issues (see Problems with mode"wt"
section below).Sample app to reproduce the issue
I've attached a sample app which demonstrates the issue. Please do the following:
Tap
Save large file
. This launches the SAF. Make sure you're in Downloads and tapSave
. This will toast "Large file save complete" when the save has finished.Tap
Verify large file
. This launches the SAF. Tap the file you saved in step 1. This should toast "Verified", meaning the file written in step 1 has the expected contents.Tap
Overwrite large file with small file
. This launches the SAF. Tap the file you saved in step 1 (this is important), then tapSave
. You should see a dialog saying "Overwrite file?", tapOk
. This will toast "Small file save complete" when the save has finished.Tap
Verify small file
. This launches the SAF. Tap the file you saved in step 1 / 3. This will toast either "Verified", meaning the file written in step 3 has the expected contents, or "Small file corrupted", meaning it does not have the expected contents.Result
Following the above steps on Android 9 and below results in step 4 showing "Verified".
Following the above steps on Android 10+ results in step 4 showing "Small file corrupted".
AOSP commit that caused this behavior change
ParcelFileDescriptor.parseMode
which equated mode"w"
with mode"wt"
, both of which includedMODE_TRUNCATE
:This logic was moved to
FileUtils.translateModeStringToPosix
where mode"w"
now only results in POSIX modesO_WRONLY | O_CREAT
, dropping truncate. To includeO_TRUNC
, a"t"
must exist in the mode.Was this an intentional change?
While this seems to have been an intentional change, I'm not 100% sure. There is a test for mode
"w"
that specifically does not includeMODE_TRUNCATE
, however, for some reason it includesMODE_CREATE
twice:It does make sense that there should be a way to open a file for writing without always truncating the existing file. However, I would not expect this to be the default behavior for . The general use case when writing a file is completely replacing any existing file, not modifying some bytes in the existing file.
ContentResolver.openOutputStream
Problems with mode
"wt"
when usingContentResolver
/ContentProvider
It might seem that a solution is to explicitly use mode , as this is listed as a valid mode in the documentation, this may not always work when using a
"wt"
instead of"w"
if your intention is to replace the existing file. While this would definitely work forParcelFileDescriptor.parseMode
ContentResolver.open*
API.For example, if you change the provided sample app to use the mode
"wt"
, then tap one of the save buttons and navigate to Google Drive in the SAF and try to save the file, the app crashes with the exception:My guess is because
"wt"
is not listed as a mode in the documentation ofContentProvider
(as mentioned above), Google Drive does not support this mode. However, from my testing, Google Drive does actually treat mode"w"
the same way that Android 9 and below did (with truncate).However, when trying to support any
ContentProvider
for all API levels, this leaves the user in a predicament. Android 10+ requires mode"wt"
, but Google Drive crashes with this mode.It turns out, mode
"rwt"
appears to work for both. My concern with this is that it's requesting read permission, which is unnecessary. While I have not discovered any issues (yet), I'm not sure if there is aContentProvider
which would balk at the read request.Summary / Actions
It would be great to know if this was an intentional behavior change.
It would be helpful if this behavior change was documented on the Behavior Changes in Android 10 page.
It would be helpful if the documentation of , assuming that's the source of truth.
ContentResolver
andContentProvider
was updated to include mode"wt"
and in general make sure that all the supported modes are listed and documented and align withParcelFileDescriptor.parseMode
Since we can't use mode
"wt"
withContentProvider
s like Google Drive (at least until Drive is updated to support it), it would be helpful to know what the recommended mode is. Are there any potential problems with using"rwt"
or is it not a problem to include read permission?My suggestion is to update the default mode used by to include truncate (although I'm not sure if this should be with the desired mode (along with better documentation for the modes).
ContentResolver.openOutputStream
"wt"
or"rwt"
due to the issues with Google Drive and potentially otherContentProvider
s). Alternatively, the mode could be left as"w"
and the documentation updated to make it clear that the behavior of this method changed in Android 10 and that the user should instead callContentResolver.openOutputStream
The extra
MODE_CREATE
should be removed from the test for mode"w"
.Additional links
There's a react-native-fs thread discussing the issue (which is where I discovered the issue with mode
"wt"
and Google Drive).I also found a related issue reported recently.