Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[file_selector_android] Modifies getDirectoryPath, openFile, openFiles to return file/directory paths instead of URIs #6438

Merged
merged 24 commits into from
Apr 29, 2024

Conversation

camsim99
Copy link
Contributor

@camsim99 camsim99 commented Mar 29, 2024

Overview

Changes getDirectoryPath, openFile, and openFiles to return the directory path instead of the URI representing the directory or file.

Fixes flutter/flutter#141250.

Technical Details

Technically, getDirectoryPath returns the path of the selected directory directly, whereas openFile and openFiles actually copy the content that the URI points to into another file and returns the path of the file. This is a direct result of the fact that Android highly discourages folks from accessing files through direct paths, so it's extremely difficult to do reliably without this approach (can confirm by how much effort it took to get this PR out...).

This approach is also used in image_picker_android and this PR uses essentially the same logic.

I think we should move forward with this short term solution to unblock folks that need access to directories and files in Dart, but I strongly advise that we re-evaluate for the long term. I elaborate on this in this doc and welcome feedback!

Pre-launch Checklist

@camsim99 camsim99 changed the title Add fix [file_selector_android] Modifies methods to return file/directory paths instead of URI Mar 29, 2024
@camsim99 camsim99 changed the title [file_selector_android] Modifies methods to return file/directory paths instead of URI [file_selector_android] Modifies getDirectoryPath to return file/directory paths instead of URI Mar 29, 2024
@camsim99 camsim99 changed the title [file_selector_android] Modifies getDirectoryPath to return file/directory paths instead of URI [file_selector_android] Modifies getDirectoryPath, openFile, & openFiles to return file/directory paths instead of URI Apr 17, 2024
@camsim99 camsim99 changed the title [file_selector_android] Modifies getDirectoryPath, openFile, & openFiles to return file/directory paths instead of URI [file_selector_android] Modifies getDirectoryPath, openFile, openFiles to return file/directory paths instead of URI Apr 17, 2024
@camsim99 camsim99 changed the title [file_selector_android] Modifies getDirectoryPath, openFile, openFiles to return file/directory paths instead of URI [file_selector_android] Modifies getDirectoryPath, openFile, openFiles to return file/directory paths instead of URIs Apr 17, 2024
@camsim99 camsim99 marked this pull request as ready for review April 17, 2024 20:55
@camsim99 camsim99 requested a review from gmackall as a code owner April 17, 2024 20:55
@camsim99 camsim99 requested a review from a team April 17, 2024 20:56
Copy link
Member

@gmackall gmackall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly lgtm, one comment on potential null pointer exceptions. I mostly skipped over the FileUtils.java and corresponding test because I'm assuming its copied over from image picker, let me know if thats not the case.

@camsim99
Copy link
Contributor Author

@gmackall to respond to your comment and clarify for any reviewers, the FileUtils.java that I added is different from that used in image_picker in the following ways:

  1. I wrote the getPathFromUri method, so please review!
  2. getPathFromCopyOfFileFromUri is the same logic used in image_picker except I removed image-specific assumptions (the only notable one being an assumption about the extension for files being .jpeg if it could not otherwise be found).

In short, a review on the whole file wouldn't hurt!

Copy link
Member

@gmackall gmackall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some more comments on FileUtils.java!

camsim99 and others added 2 commits April 18, 2024 12:19
…java/dev/flutter/packages/file_selector_android/FileUtils.java

Co-authored-by: Gray Mackall <34871572+gmackall@users.noreply.github.com>
…java/dev/flutter/packages/file_selector_android/FileUtils.java

Co-authored-by: Gray Mackall <34871572+gmackall@users.noreply.github.com>
String uriDocumentId = DocumentsContract.getDocumentId(uri);
String documentStorageVolume = uriDocumentId.split(":")[0];

// Non-primary storage volumes come from SD cards, USB drives, etc. and are
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are they handled? Also can you include a link to your source that "primary" is the correct string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"primary" is the correct String because this constant is used as the root of the document ID. I verified in my own testing and looking through examples of getting the getting paths from URIs. There is no documentation about it probably because Android does not want folks accessing files this way.

I did not handle those cases because I honestly didn't think we needed to handle these cases in this plugin, but now that you mention it, there was never that restriction. I would prefer to leave this as a TODO and file an issue, though, because I would have to figure out how to test this myself and do some similar deducing since there's no documentation and wouldn't like to block this PR on that but let me know what you think.

I think that this entire comment is demonstrative of why we should prioritize finding a longer term solution that isn't reliant on file paths BTW :) it will be hard for us to find future-proof solutions and handle all cases with this approach.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should link that constant in this code because that will be super hard to find later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also my understanding of this api is that a user is picking something and the host app has no control over what a user picks. Throwing an exception doesn't feel wrong but it seems odd to not give a caller a way to control or filter what users can see to the files that the caller can actually access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make a good point. In theory, the host app should not be tasked to control what kind of files the user picks and the plugin should just take care of it. So, the exception is meant to signal an issue with the plugin for users to report to us to handle. I don't love that solution but I do not feel like silently failing is much better since we won't find out as fast about the issues. Maybe we can catch the exception and send back a dummy value to the Flutter app that signifies an error was encountered?

Again, this issue is specific to this approach of using file paths because Android should handle all relevant file types in its storage framework.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me back up to a more simple statement. If a developer uses this plugin on android can their user select files that would cause us to throw this exception? My understanding of the codepaths is yes, that the file selector utility lets people pick things from anywhere on their device.

I get that a silent failure is not ok. What I am hearing in your answer is that the caller cannot avoid these errors but also they are outside of what you think the plugin can handle in this pr? Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a developer uses this plugin on android can their user select files that would cause us to throw this exception?

You're right -- theoretically yes, but we do not expect it. This is only the answer because I am not on the Android team with this sort of insight and no one else is besides them.

What I am hearing in your answer is that the caller cannot avoid these errors but also they are outside of what you think the plugin can handle in this pr? Is that correct?

Yes -- I argue that we spend more time on a long term solution. There's no further progress I can make on this PR to avoid those errors until they are encountered, unfortunately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok those seem to be reasonable arguments. Merge away

@Nullable
public static String getPathFromCopyOfFileFromUri(@NonNull Context context, @NonNull Uri uri) {
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
String uuid = UUID.randomUUID().toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing random things can be hard consider having a way to set a seed that can be used in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to a UUID based on the URI and updated FileSelectorAndroidTest to check for the expected path since we can now reproduce it in tests!

Copy link
Member

@gmackall gmackall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Copy link
Contributor

@reidbaker reidbaker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One philosophy question about how we should signal to callers what to do. But the code looks good.

String uriDocumentId = DocumentsContract.getDocumentId(uri);
String documentStorageVolume = uriDocumentId.split(":")[0];

// Non-primary storage volumes come from SD cards, USB drives, etc. and are
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should link that constant in this code because that will be super hard to find later.

String uriDocumentId = DocumentsContract.getDocumentId(uri);
String documentStorageVolume = uriDocumentId.split(":")[0];

// Non-primary storage volumes come from SD cards, USB drives, etc. and are
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also my understanding of this api is that a user is picking something and the host app has no control over what a user picks. Throwing an exception doesn't feel wrong but it seems odd to not give a caller a way to control or filter what users can see to the files that the caller can actually access.

@camsim99 camsim99 added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 29, 2024
@auto-submit auto-submit bot merged commit 36c42bd into flutter:main Apr 29, 2024
78 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Apr 30, 2024
…ile`, `openFiles` to return file/directory paths instead of URIs (flutter/packages#6438)
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Apr 30, 2024
flutter/packages@87a7c51...cc47b06

2024-04-30 joonas.kerttula@codemate.com [google_maps_flutter_web] Add marker clustering support (flutter/packages#6187)
2024-04-30 joonas.kerttula@codemate.com [google_maps_flutter_android] Add marker clustering support (flutter/packages#6185)
2024-04-29 32538273+ValentinVignal@users.noreply.github.com [go_router] Don't log if `hierarchicalLoggingEnabled` is `true`  (flutter/packages#6019)
2024-04-29 43054281+camsim99@users.noreply.github.com [file_selector_android] Update `LICENSE` file to include newly added licensed code (flutter/packages#6626)
2024-04-29 43054281+camsim99@users.noreply.github.com [file_selector_android] Modifies `getDirectoryPath`, `openFile`, `openFiles` to return file/directory paths instead of URIs (flutter/packages#6438)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com,rmistry@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
TecHaxter pushed a commit to TecHaxter/flutter_packages that referenced this pull request May 22, 2024
…nFiles` to return file/directory paths instead of URIs (flutter#6438)

## Overview

Changes `getDirectoryPath`, `openFile`, and `openFiles` to return the directory path instead of the URI representing the directory or file.

Fixes flutter/flutter#141250.

## Technical Details

Technically, `getDirectoryPath` returns the path of the selected directory directly, whereas `openFile` and `openFiles` actually copy the content that the URI points to into another file and returns the path of the file. This is a direct result of the fact that Android **highly discourages** folks from accessing files through direct paths, so it's extremely difficult to do reliably without this approach (can confirm by how much effort it took to get this PR out...).

This approach [is also used in `image_picker_android`](https://github.com/flutter/packages/blob/main/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java) and this PR uses essentially the same logic.

I think we should move forward with this short term solution to unblock folks that need access to directories and files in Dart, but I strongly advise that we re-evaluate for the long term. I elaborate on this in [this doc](https://docs.google.com/document/d/12pJDtl0yubyc68UqKo2hQ7XJwv86_ixCjNemQ7ENCBQ/edit?usp=sharing) and welcome feedback!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: file_selector platform-android
Projects
None yet
3 participants