Skip to content

Expose a patched app's private data to file managers without root - #101

Merged
JingMatrix merged 1 commit into
masterfrom
documents-provider
Aug 17, 2026
Merged

Expose a patched app's private data to file managers without root#101
JingMatrix merged 1 commit into
masterfrom
documents-provider

Conversation

@JingMatrix

Copy link
Copy Markdown
Owner

A module -- or the user -- often needs to reach a patched app's private data, and only that app's own UID can. This adds an opt-in that injects a DocumentsProvider running inside the app, serving its data directory through the Storage Access Framework, so any SAF file manager can browse it with no root. It is off by default and clearly an expert option, since it widens what can reach the app's data.

The provider is declared per-package behind MANAGE_DOCUMENTS, so only the system Documents UI can bind it and access reaches other apps only through a granted document or tree. Its exported/grantUriPermissions are written as real booleans -- a string "true" is read back as false and silently un-exports it -- which is why core is bumped to the ManifestEditor that emits typed attributes. Because the platform instantiates the provider from the app's own class loader, which never sees the loader's dex, a filtering loader is spliced in as its parent to resolve that one class and defer everything else.

Recorded in the patch config so a re-patch keeps it. Closes #65.

A module -- or the user -- often needs to reach a patched app's private data, and only that app's own
UID can. A patch can now inject a DocumentsProvider that runs inside the app and serves its data
directory through the Storage Access Framework, so any file manager that speaks SAF can browse it with
no root. It is off by default and clearly an expert option, since it widens what can reach the app's
data.

The provider is declared in the manifest as a per-package authority behind the MANAGE_DOCUMENTS
permission -- the one the system Documents UI holds -- so only the system can bind it and access
reaches other apps only through the user granting a document or tree. Its exported and
grantUriPermissions attributes must be written as real booleans: the platform reads them with
TypedArray.getBoolean, which returns the default for a string, so a boolean spelled "true" silently
un-exports the provider. The vendored ManifestEditor could only add providers as string pairs; core is
bumped to the version that emits provider attributes with their real types.

The harder half is that the platform instantiates a manifest-declared provider from the app's own
class loader, which holds only the original apk and its splits -- never the loader's in-memory dex,
where the provider class lives -- so installContentProviders failed the class at startup. The class
cannot simply be grafted onto the app loader's dex path: ART binds an in-memory dex to the class-loader
context it was defined under, and defining the class a second time elsewhere is rejected. Instead a
filtering loader is spliced in as the app loader's parent; standard delegation consults it on every
lookup, but it answers with exactly one class -- the provider, loaded by the loader that already owns
it -- and defers everything else, so the app's own classes still resolve from its own dexes unchanged.

The choice is recorded in the patched app's config, like the added permissions, so a re-patch that
recovers only the original apks keeps it. The CLI gains --documents-provider; the manager's advanced
sheet gains a toggle, and the permission editor moves below it -- its chip list is the tallest control
in the group, so it sits last rather than pushing the compact toggles down. Strings are translated
across every shipped locale.

Closes #65
@JingMatrix
JingMatrix merged commit 945585d into master Aug 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] inject document provider

1 participant