Repository navigation
Description
What happens
Opening an office document from Twig with "Open with another application" gives the editor a read-only URI: the document loads, but it cannot be saved back (the editor offers no save, or saving fails). Reproduced today with a .xlsx on an Android/HONOR phone: another file manager on the same device hands the same file over so that the editor can save it, so the difference is in Twig.
Twig 1.17.0 (F-Droid), Android 12+.
Why
app/src/main/kotlin/com/twig/app/OpenFiles.kt — openWith() (line ~217) and openWithComponent() (line ~249) both build
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(StreamProvider.uriFor(context, file), mimeOf(file.name))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}No FLAG_GRANT_WRITE_URI_PERMISSION, no ACTION_EDIT. And the URI handed over is a StreamProvider URI, which is deliberately a read-only stream (proxy fd / one-way pipe, see the class comment) — so granting the write flag alone would only move the failure to save time.
Proposal
Two levels, the first one cheap and safe:
1. Hand writable sources over directly (no provider work). When the underlying source is itself writable — a local File or a SAF document picked via ACTION_OPEN_DOCUMENT/tree access — do not wrap it in StreamProvider; pass the original content:// document URI (SAF) or a FileProvider URI (local, with FLAG_GRANT_WRITE_URI_PERMISSION) to the editor, with
val intent = Intent(Intent.ACTION_EDIT).apply { // ACTION_EDIT for documents, ACTION_VIEW for media
setDataAndType(target, mimeOf(file.name))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}The editor then writes into the original file, and Twig only has to refresh the pane when it comes back (already the case for other flows). DOC_EXT in the same file already separates documents from media, so the choice of action is a one-liner.
2. Writable StreamProvider (later, for SMB/WebDAV/SFTP sources). For sources whose only handle is a byte stream, write support means the provider must accept write access itself: openFile with mode w/rw returning a temp file that is uploaded on close/release, plus DocumentsContract.Document.FLAG_SUPPORTS_WRITE in query() so editors that check capabilities enable their save path. That is a real feature, not a flag — which is why I am not proposing it as the quick fix.
3. In both cases ACTION_EDIT matters for the resolver: it is what makes the system offer editors (and what some office apps require to open in edit mode rather than read-only).
Not a duplicate (as far as I can tell)
I searched the open issues for write, read-only/readonly, "edit in place" and found nothing about the URI permission of the Open-with path. Issues #14/#15/#16/#17 are about other edit surfaces (hex/database/properties/patches).
Happy to test a build on the device that reproduced it, and to report what each source type does (local file, SAF/Nextcloud, SMB).
Update: a resolved default app does not change it (2026-11-09, evening)
Setting Collabora Office as the system default for the spreadsheet type (HONOR MagicOS:
Einstellungen → Apps → Standard-Apps → Weitere Standard-Apps → Excel-Dokument) does make Twig start Collabora
directly — no chooser, as expected — but the document still opens read-only. So the resolver is not the problem:
the intent and the URI grant are.
Concrete use case to reproduce (Nextcloud + Collabora Office)
- Nextcloud (self-hosted, 35.x) with an
.xlsxin a folder, opened in Twig both ways — through the SAF
document tree and through Twig's own WebDAV source. Both were tested on 2026-11-09 and both open read-only, so
the source is not the variable. (A local copy under/sdcard/Downloadhas not been tested yet; it is not needed to
reproduce.) - In Twig, open that
.xlsx→ "Öffnen mit anderer Anwendung" / Open with another application → Collabora
Office (Play Store, current version). - Collabora opens the sheet in read-only/view mode: no editing, no save-back.
The control that isolates the cause
On the same device, the same file, to the same Collabora: another file manager (Ghost Commander) handed it over
and Collabora opened it in an editor that could be used — i.e. not the view-only state Twig produces. Only Twig's path
is read-only, which rules out Collabora, the file and Android's resolver, and leaves the intent/permission Twig builds
(OpenFiles.openWith(), ACTION_VIEW + FLAG_GRANT_READ_URI_PERMISSION, over a read-only StreamProvider URI). The
comparison is now verified end to end (2026-11-09, evening): with Material Files, the same Nextcloud document
opened through the platform's SAF tree — with the folder granted rather than the account root — opened in Collabora,
accepted an edit, and the save arrived in Nextcloud (13 rows, 6 474 B, previous state kept as a version). An editor
therefore can write back through the SAF path on this device with this Nextcloud; Twig's hand-over is the remaining
difference. (Material Files' own WebDAV source does not save either — it left a 1 024 B file — so the SAF path is the one
that works.)
Two layers, worth separating when fixing
- Action: an editor is opened with
ACTION_VIEW, and some editors (Collabora among them) decide view mode from
that alone.ACTION_EDITforDOC_EXTtypes costs nothing and is a hint the editor understands. - Permission/capability: even with
ACTION_EDITand a write grant, the receiving app has to be able to open the
URI for writing.StreamProvideris a read-only stream by design, so for SMB/WebDAV/SFTP sources this needs the
writable-provider work described above; for a local file or an SAF document it needs no provider work at all — the
editor can be given the original URI (or aFileProviderURI with write) instead of the streaming one.
A quick way to tell which layer a given file type hits: if the editor shows its own "read-only" banner while the
system dialog offered write permission, it is the action; if the editor offers "Save" and the save then fails, it is
the provider.
#AI-ASSISTED — this report was drafted with AI assistance; the source references and the reproduced behaviour were checked against main and the device.
Activity
dev2ex commentedon Nov 9, 2026
Thanks for testing this so thoroughly, especially checking that the edit actually made it back to Nextcloud through the SAF path. That comparison helps pin this down.
I checked the current code and confirmed that Twig only grants read access, and StreamProvider rejects writes even when the source itself is writable.
I’ll address local files and SAF documents first, so editors can save back to the original file. That includes your Nextcloud case when opened through its SAF document tree. I’ll also handle the edit intent consistently across the regular Open with flow and opening with a specific app, with a viewing fallback for apps that do not support ACTION_EDIT.
Save-back through Twig’s own WebDAV, SMB and SFTP sources will remain outside this first fix; those need a separate write-back implementation. I’ll post a test build here when the local/SAF changes are ready.
PeterPablo commentedon Nov 9, 2026
wow, great to see you responding so fast. For me it was first crucial to understand state of this project -- I have only installed it today and like twig a lot from first "touch point".
I'll thus keep eyes on this issue, will test and report back.
Honestly: the thoroughness got only enabled from me since two weeks pairing my local IT experiments/tasks with DeepSeek Flash in VSCode. It is affordable and let's me root cause aspects I would otherwise really have no time to dig into. It on its own pulled the sources, based on my factual description on where things behave differently then expected, and precisely located the code snippet. I have no Android development setup here, albeit I might try it myself...
So thank you again for the prompt feedback signaling willingness to look into this!
Note: above bug report got posted without me changing a single character. It was AI created (as stated -- this is my requirement, a best practice from my day job and in my opinion fair, so you can judge on if you are willing to look at it at all).
PeterPablo commentedon Nov 9, 2026
A follow-up from the same phone, because it isolates the cause further: the same Nextcloud document, opened through a different file manager, is editable and the edit lands back in Nextcloud.
The working combination is Material Files, with the folder granted through Nextcloud's SAF document tree (not the account root — see the caveat below). Measured: open the
.xlsx, change one cell in Collabora Office, save, then check from the web UI — 13 rows, 6,474 B, fresh mtime, valid package.What differs is exactly the two layers you described, and Material Files shows both halves:
OpenFileActivity.kt#L39-L40andOpenFileAsDialogFragment.kt#L43-L44addFLAG_GRANT_WRITE_URI_PERMISSIONto theACTION_VIEWintent, andIntentExtensions.kt#L103-L106sendsACTION_EDITwith read+write for the edit flow.FileProvider.kt#L188openFile(uri, mode), and for a path that cannot be opened directly (an SAF document)modeBits.toOpenOptions()at#L195, so the write returns to whichever provider owns the document.So a proxying provider can support write-back — the URI it hands out is not the obstacle, a one-way
StreamProvideris. If Twig re-opens the source document with theParcelFileDescriptormode translated the way thatopenFiledoes, our Nextcloud case should need nothing specific to Nextcloud.One caveat that is not yours, in case it looks like the same bug while you test: in Material Files, granting the Nextcloud account root lists every folder twice and fails with
java.nio.fileSystemException: /tmp: java.lang.InterruptedException— that is nextcloud/android#17051; granting a subfolder works. The read-only symptom and that root-level defect are independent, so a local file or a subfolder grant is enough to test the write path.#AI-ASSISTED
dev2ex commentedon Nov 9, 2026
Thanks for sharing that — AI assistance is absolutely fine with me. What matters is having a clear description and reproducible behavior, and your checks on the phone have been really helpful.
The Material Files comparison also clarifies that ACTION_VIEW can work with a writable URI and the right grants. I’ve made the local/SAF changes, including text files, and the automated tests pass. The Collabora + Nextcloud save-back still needs testing on a real device, so your offer to try a build is much appreciated.
And thanks for the heads-up about the Nextcloud account-root issue. We’ll use a subfolder grant when testing. I’ll share a build here once it’s ready.
dev2ex commentedon Nov 9, 2026
The test build is ready: 1.18.1-beta.2. It includes the local/SAF fix for documents and text files, including opening through existing app-specific desktop shortcuts.
For your Nextcloud + Collabora case, please grant a subfolder through SAF, open the spreadsheet in Collabora, make a change and save, then check whether it arrived in Nextcloud’s web UI. Trying a local file as well would help distinguish any remaining hand-off issue from provider synchronization.
Twig’s own WebDAV, SMB and SFTP sources will continue to open read-only in external apps. We’re keeping that boundary to avoid local working copies silently overwriting newer server changes or presenting a local save as a completed upload. The SAF path delegates saving and synchronization to the original document provider.
Thanks again for offering to test — I’d be glad to hear how this build behaves on your phone.
PeterPablo commentedon Nov 9, 2026
Device check passed —
798191bdfixes the Nextcloud/SAF case. Prose from #61 said read-only through Twig; this is the same document, same phone, same editor, one version later.Setup: Honor 200 (
ELP-NX9), Android 16, Twig 1.18.1-beta.2 (versionCode 321) from the preview APK — signed with the same key as the installed release, so it was a normal update. Path taken: Dokumentenbaum (SAF) → Nextcloud →tmp→.xlsx→ Collabora Office.…v1794250639, 9137 B, 20:11:10) — so the write went to the original document, not to a cache copy.The package is valid, not merely non-empty — this was the failure mode of the other editor I tried (MA Office wrote an unfinalised mixed package):
Sheet1intacttest via new twigxl/theme/theme.xmlbecamexl/theme/theme1.xml, with[Content_Types].xmlandxl/_rels/workbook.xml.relspointing at the new name — no dangling reference, which is also why the file got smallerOne leftover, not this fix's business: with the account-root grant the SAF listing still shows every folder twice while opening and saving work fine — the
nextcloud/android#17051symptom, unchanged. Subfolder grants were never involved this time.Logcat was no help: the buffer had rolled by the time I read it, so there is no intent dump. The file and its version are the stronger evidence anyway — a write that reached the wrong bytes would not produce row 18 and a consistent package.
Thanks for turning this around the same evening. #61 can be considered answered from my side.
#AI-ASSISTED
PeterPablo commentedon Nov 9, 2026
This is optically inconvenient. I shall raise a new issue for that and try compiling myself (again). I did in the meantime compile the app on my laptop, so it seems I would be ready to even compile things locally and load them using adb. nice. :-)
Thank you again for the very quick turnaround! I guess I leave closing this issue to you, in case you want to follow up on something.
PeterPablo commentedon Nov 9, 2026
One more data point, then I am quiet: the same document from a document-tree mount rooted at
tmpinstead of the account root — same result, with its own fingerprint.test twig from tmpSheet1intact…v1794251471(6232 B, 20:19:52)So both grant levels write on 1.18.1-beta.2. The difference between them is only the doubled folder listing in the account-root case — and that is more than
nextcloud/android#17051reports for the other file manager I compared, which could not opentmpfrom the root grant at all (java.nio.fileSystemException). I read that as: with this fix the duplication is a display defect in Twig, not a functional one.#AI-ASSISTED
PeterPablo commentedon Nov 9, 2026
Closing the loop on the leftover I mentioned above, so nobody has to file a follow-up issue for it:
DocumentsProvider: two rows in itsfilelisttable for onepath/file_owner, and the file list query (WHERE parent = ?) has no owner filter and noDISTINCT, so both rows are handed out. Twig maps that cursor 1:1 — which is exactly why the doubling showed up here so plainly.UNIQUE INDEXon(path, file_owner)in place, so they cannot come back.Thanks again — the writable-document handover works flawlessly here, and it is the reason the edit-and-save path is usable at all from a file manager.
#AI-ASSISTED
dev2ex commentedon Nov 10, 2026
Thanks for the three rounds of device checks — the account-root grant, the
tmp-rooted grant, and the provider-level analysis of the doubled folders. That is exactly the evidence that made the remaining question answerable, and the byte-level evidence (row 18/19 present, a valid package, Nextcloud keeping the replaced state as a version) is stronger than any logcat dump.Status: fixed on the device —
798191bdin 1.18.1-beta.2. Local files and SAF documents now hand the original handle to the editor (ACTION_EDIT+ write grant), so the editor writes into the document its provider owns, and Nextcloud's versioning sees the change.The doubled folder listing: understood, and agreed — it is not a Twig defect. The duplicate rows come from the provider's
filelisttable (two rows for onepath/file_owner, and theWHERE parent = ?query has neither an owner filter norDISTINCT), and Twig maps the cursor 1:1, which is precisely why the doubling was visible here. Your nextcloud/android#18001 — the cleanup migration plus aUNIQUE INDEXon(path, file_owner)— fixes the root cause, and we will keep the 1:1 mapping rather than papering over it with a de-duplication that would hide genuine entries from other providers.One boundary stays as announced: Twig's own WebDAV, SMB and SFTP sources still open read-only in external apps. A writable proxy means write-back, temporary-file semantics and an upload on close, and we would rather not present a local save as a completed upload. If that becomes worth doing, it belongs in its own issue.
Closing this one as fixed — thank you for the report, the control comparisons and the root-causing of the leftover.
中文翻译:
感谢你做了三轮设备验证——账户根授权、
tmp为根的授权,以及对重复文件夹的 provider 层分析。正是这些证据让剩下的问题得以有定论,而字节级证据(第 18/19 行确实存在、包结构有效、Nextcloud 把被替换的状态存为历史版本)比任何 logcat 转储都更有说服力。状态: 已在设备上修复——
798191bd,包含在 1.18.1-beta.2 中。本地文件与 SAF 文档现在把原始句柄交给编辑器(ACTION_EDIT+ 写授权),于是编辑器直接写入其 provider 所拥有的文档,Nextcloud 的版本机制也能看到这次改动。重复的文件夹列表: 已理解,也认同——这不是 Twig 的缺陷。重复行来自 provider 的
filelist表(同一个path/file_owner有两行,而WHERE parent = ?查询既没有 owner 过滤也没有DISTINCT),而 Twig 是 1:1 映射游标,这正是重复在这里格外显眼的原因。你的 nextcloud/android#18001——清理迁移加上(path, file_owner)上的UNIQUE INDEX——修掉了根因,我们会保留 1:1 映射,而不是用一个会把其他 provider 的真实条目也一起藏起来的去重来掩盖它。一条边界仍按先前说明保留: Twig 自带的 WebDAV、SMB 和 SFTP 源在外部应用中仍以只读方式打开。可写代理意味着写回、临时文件语义以及关闭时上传,我们宁愿不把一次本地保存说成已完成的上传。如果这块值得做,应该单开一个 issue。
这个 issue 就按"已修复"关闭——感谢你的报告、对照实验,以及对遗留问题的根因分析。