Browse the folders you have granted access to - #527
Draft
andiwand wants to merge 1 commit into
Draft
Conversation
andiwand
force-pushed
the
landing/04-direct-picker
branch
from
July 27, 2026 20:40
2a58ee6 to
9ace181
Compare
andiwand
force-pushed
the
landing/05-folder-browsing
branch
from
July 27, 2026 20:40
ca39104 to
982f294
Compare
"See the files on my phone" has no permission behind it any more. READ_EXTERNAL_STORAGE has not reached documents since scoped storage, READ_MEDIA_* covers only images, video and audio, and Play restricts MANAGE_EXTERNAL_STORAGE to file managers and backup apps - a document viewer asking for it gets the listing rejected. The manifest still declares nothing but INTERNET, and it stays that way. ACTION_OPEN_DOCUMENT_TREE does the same job with the user's consent instead: they pick a folder once, we persist a read only grant, and the landing screen lists and navigates it from then on. Read only - no write flag is ever requested. Listing goes through DocumentsContract rather than androidx's DocumentFile, which issues a fresh query per child the moment a name or type is read. One cursor covers a whole folder. SupportedDocumentTypes decides what is worth showing. It has to guess: MetadataLoader only knows what is supported after libmagic has run over the cached file, and a folder listing has nothing but a name and whatever mime type the provider volunteered - which is regularly application/octet-stream, hence the extension fallback. It mirrors the STRICT_CATCH intent filter and nothing keeps the two in step, so both sides now say so, as does CLAUDE.md. prune() learns that a document below a granted tree is covered by that tree's grant, which it can tell from the uri alone - so nothing has to record where a document was opened from. The tree grant is taken through PersistedUriPermissions rather than the resolver directly, so that it joins the in flight set the recent list's grants already use: addFolderTree writes the tree down on the view model's executor, and a prune in that window would find a grant nothing points at yet and hand it straight back. Same race, same fix, one folder instead of one document. That decision is now isReferenced(), which takes the strings rather than a context and so can be covered by a plain jvm test, with the release half lifted out beside it to keep prune() down to the loop it is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiSZ4FeptMUsFjAuCewzQZ
andiwand
force-pushed
the
landing/04-direct-picker
branch
from
July 27, 2026 22:01
9ace181 to
870bcce
Compare
andiwand
force-pushed
the
landing/05-folder-browsing
branch
from
July 27, 2026 22:01
982f294 to
f007dab
Compare
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.
Fifth of six, stacked. Base:
landing/04-direct-picker(#526)."See the files on my phone" has no permission behind it any more.
READ_EXTERNAL_STORAGEhas not reached documents since scoped storage,READ_MEDIA_*covers only images/video/audio, and Play restrictsMANAGE_EXTERNAL_STORAGEto file managers and backup apps — a document viewer asking for it gets the listing rejected. The manifest still declares nothing butINTERNET, and it stays that way.ACTION_OPEN_DOCUMENT_TREEdoes the same job with the user's consent instead: pick a folder once, we persist a read-only grant, and the landing screen lists and navigates it from then on. No write flag is ever requested.DocumentsContract, not androidxDocumentFile— the latter issues a fresh query per child the moment a name or type is read. One cursor covers a whole folder.SupportedDocumentTypeshas to guess, and says so.MetadataLoaderonly knows what is supported after libmagic has run over the cached file; a folder listing has a name and whatever mime type the provider volunteered — regularlyapplication/octet-stream, hence the extension fallback. It mirrors theSTRICT_CATCHintent filter and nothing keeps the two in step, so both sides now cross-reference each other, as does CLAUDE.md.prune()learns the tree prefix rule. A document below a granted tree is covered by that tree's grant, and its uri spells the tree out — so nothing has to record where a document was opened from, andFileLoader.Optionsneeds no new field.PersistedUriPermissions, not the resolver.addFolderTreewrites the tree down on the view model's executor, so aprune()in that window would find a grant nothing points at yet and hand it straight back. That is the same race Hold on to a grant until the recent list names it #540 closed for the recent list, and taking the grant through the shared helper puts the folder in the same in-flight set.prune()'s decision is nowisReferenced(), which takes strings rather than aContextso a plain JVM test covers it, with the release half lifted out beside it.Verification
20 instrumented tests green, and
PersistedUriPermissionsTestnow covers the tree prefix rule (including a sibling tree that merely shares a prefix). On a device, through the real DocumentsUI picker: grantedDocuments, browsed into it and into a subfolder, confirmed an.mp3was filtered out and directories sorted first, back went up a level rather than out of the app, opened a document from the folder and it landed in RECENT — which is theisRetainedfallback doing its job, sincetakePersistableUriPermissionthrows on a tree-child uri.🤖 Generated with Claude Code
https://claude.ai/code/session_01LrDgmtcnqm8xEUtt42cTDd