Skip to content

Open every format the core renders, and ask it what can be edited - #548

Open
andiwand wants to merge 4 commits into
mainfrom
core/formats-and-editability
Open

Open every format the core renders, and ask it what can be edited#548
andiwand wants to merge 4 commits into
mainfrom
core/formats-and-editability

Conversation

@andiwand

Copy link
Copy Markdown
Member

The two commits out of #547 that have nothing to do with the redesign. Both were
reviewed there already; they are here so the redesign PR is only the UI work, and so
this can go out on its own.

Open every format the core renders (#546)

odrcore v6 keeps reference html output for doc, ppt, xls and pptx next to the ones the
app already claimed, so the TODO: enable pptx too in CoreLoader was the only thing
keeping presentations out - and the legacy binary formats reached the core only by
accident, through the doc mime type that was listed before ppt and xls ever were.

What the loader claims is now one list: opendocument, the whole ooxml family, the three
legacy microsoft formats and pdf. Prefixes, so the templates and the macro enabled
variants come along - the latter are spelled vnd.ms-word/vnd.ms-excel/
vnd.ms-powerpoint rather than under the openxmlformats prefix, which is why those
appear without their subtypes. Text, csv and images stay out: the core renders them too,
but RawLoader is what gives them their player, and it only gets a turn when the core
loader says no.

The STRICT_CATCH filter follows, so the system offers us pptx/ppt/xls.

Two things fall out of it. The doOoxml flag is gone - it has been passed true since
ooxml stopped being experimental and now gates nothing. And pdf changes which viewer a
failed upload falls back to, so that stays explicit: google docs still gets the pdf, the
microsoft viewer keeps everything else the core knows.

Editability is the core's answer, not a mime type

doc, ppt and xls now reach CoreLoader, and prepareMenu offered Edit for every CORE
result except ODS and PDF - so the legacy formats got a button that could only ever end
in a failed save, because host() would not keep a document open for them.

The list of exceptions was never ours to keep. Document.isEditable()/isSavable() is
false for the three legacy binary formats, for ooxml spreadsheets and presentations, and
for every spreadsheet including odf - the last being the core's own TODO, the same gap as
#442 that the UI was spelling out by mime prefix. host() now keeps the document only
when the core says yes, which makes "we have a document" the answer, and it travels to the
fragment on Result.

Two more copies of the same idea go with it:

  • CoreLoader.MIME_PREFIXES moves out into SupportedDocumentTypes, which adds what
    RawLoader shows and an extension fallback for the application/octet-stream a provider
    volunteers when it knows nothing better. What stays separate is the manifest, which XML
    keeps out of reach - so SupportedFormatsTest walks odrcore's FileType values, asks
    Odr.mimetypeByFileType for each, and asserts the table and the package manager agree.
    Adding a format to one side and forgetting the other is now a red test. The ooxml
    templates, slideshows and macro enabled variants that walk cannot reach get a case of
    their own; without them spelled out in the manifest a .dotx was not offered to us at
    all.

    The core cannot replace the table itself: its lookups are native, and know one canonical
    mime per format - no templates, no macroEnabled, no x-vnd.oasis, which is exactly what
    providers hand out.

  • MainActivity kept the catch-all switch's component names, preference and toggling
    inline. That is CatchAllSetting now, so a test can ask for STRICT_CATCH without going
    through the UI - which SupportedFormatsTest has to, or a previous run that flipped the
    switch leaves CATCH_ALL answering for everything.

Tests

New: SupportedFormatsTest (instrumented), CoreLoaderTest, SupportedDocumentTypesTest,
and CoreTest coverage for .doc/.ppt/.xls/.pptx against real fixtures plus a case pinning
which of them the core reports editable. 22 instrumented tests green on device, up from 12.

🤖 Generated with Claude Code

andiwand and others added 2 commits July 29, 2026 08:53
odrcore v6 keeps reference html output for doc, ppt, xls and pptx next to
the ones the app already claimed, so the "TODO: enable pptx too" that sat
in CoreLoader was the only thing keeping presentations out - and the
legacy binary formats reached the core only by accident, through the doc
mime type that was listed before ppt and xls ever were.

What the loader claims is now one list: opendocument, the whole ooxml
family, the three legacy microsoft formats and pdf. Prefixes, so the
templates and the macro enabled variants come along - the latter are
spelled vnd.ms-word/vnd.ms-excel/vnd.ms-powerpoint rather than under the
openxmlformats prefix, which is why those appear without their subtypes.
Text, csv and images stay out: the core renders them too, but RawLoader
is what gives them their player, and it only gets a turn when the core
loader says no.

The STRICT_CATCH filter follows, so the system offers us pptx/ppt/xls.

Two things fall out of it. The doOoxml flag is gone - it has been passed
true since ooxml stopped being experimental and now gates nothing. And
the ".doc-files are not real documents" special case becomes a check for
all three legacy types: the core does parse them now, but declares them
neither editable nor savable, so keeping one open only buys a second
parse.

pdf changes which viewer a failed upload falls back to, so that stays
explicit: google docs still gets the pdf, the microsoft viewer keeps
everything else the core knows.

Verified on device - .pptx, .ppt, .doc and .xls all render through the
core, with CoreTest covering each of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eTZB8rnh9PrdNn1YZexue
doc, ppt and xls now reach CoreLoader, and prepareMenu offered Edit for
every CORE result except ODS and PDF - so the legacy formats got a button
that could only ever end in a failed save, because host() would not keep
a document open for them.

The list of exceptions was never ours to keep. odrcore answers this
itself: Document.isEditable()/isSavable() is false for the three legacy
binary formats, for ooxml spreadsheets and presentations, and for every
spreadsheet including odf - the last being the core's own TODO, the same
gap as #442 that the UI was spelling out by mime prefix. host() now keeps
the document only when the core says yes, which makes "we have a
document" the answer, and it travels to the fragment on Result.

While in there, two more copies of the same idea:

- CoreLoader.MIME_PREFIXES moves out into SupportedDocumentTypes, which
  adds what RawLoader shows and an extension fallback for the
  application/octet-stream a provider volunteers when it knows nothing
  better. What stays separate is the manifest, which XML keeps out of
  reach - so SupportedFormatsTest walks odrcore's FileType values, asks
  Odr.mimetypeByFileType for each, and asserts the table and the package
  manager agree. Adding a format to one side and forgetting the other is
  now a red test, and the ooxml templates, slideshows and macro enabled
  variants that walk cannot reach get a case of their own; without them
  spelled out in the manifest a .dotx was not offered to us at all.

  The core cannot replace the table itself: its lookups are native, and
  know one canonical mime per format - no templates, no macroEnabled, no
  x-vnd.oasis, which is exactly what providers hand out.

- MainActivity kept the catch-all switch's component names, preference
  and toggling inline. That is CatchAllSetting now, so a test can ask for
  STRICT_CATCH without going through the UI - which SupportedFormatsTest
  has to, or a previous run that flipped the switch leaves CATCH_ALL
  answering for everything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eTZB8rnh9PrdNn1YZexue

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d053b3f4f6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/main/AndroidManifest.xml
Codex on #548: the eleven ooxml variant mime types the last commit spelled
out only help a provider that sends the exact mime type. A file manager
that sends ACTION_VIEW with just a content:/file: uri falls through to the
pathPattern filters, and those listed docx, xlsx, pptx, doc, xls and ppt -
so a .dotx or .pptm was claimed by mime and not by name.

The gap was older than that, and wider: ott, ots, otp and otg have been in
SupportedDocumentTypes for as long as it has existed, with no pattern for
any of them. Nothing noticed because the only test of the extension
fallback went through application/octet-stream, which the filter claims
outright - it matches whatever the patterns happen to say, so it could
never fail on a missing one.

So aFileWithNoMimeTypeReachesUsByItsExtension asks the question that
route actually poses: no type at all, one assertion per entry of
EXTENSIONS. Verified it catches this by deleting the .ott and .dotx
patterns again - "the manifest has no pathPattern for .ott".

EXTENSIONS grows the eleven variants to match, and splits in two on the
way. odrcore's fileTypeByFileExtension names one suffix per format and
answers UNKNOWN for every one of them, though it renders them all - a
.dotx is the same wordprocessing package as a .docx, and libmagic
identifies it by content once the file is cached. CORE_EXTENSIONS is the
half held against that table; OOXML_VARIANT_EXTENSIONS is the half that
asserts the core still does not name them, so a core that learns them
turns red rather than leaving the split there for no reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eTZB8rnh9PrdNn1YZexue
The api 32 job crashed the lite run after 13 tests, in the middle of
testPDF, with a SIGSEGV inside httplib's accept loop. Chasing it turned
up two separate races, both on the teardown path, and odrcore has no safe
way through either:

  #00 httplib::Server::listen_internal()
  #1 odr::HttpServer::Impl::listen() const
  #16 CoreLoader.initialize$lambda$0

close() destroyed the server while the thread that called listen() was
still inside it. HttpServer.close() frees the native server there and
then, so that thread segfaults the next time it touches it - six seconds
and two tests later, here. Waiting first does not settle it either:
stop() only asks, and the accept loop comes back on its own time.

Waiting longer surfaced the second one, an abort rather than a segfault:

  fdsan: attempted to close file descriptor 101, expected to be unowned,
         actually owned by ZipArchive 0x73adcf9fe0
  #2 close+16
  #3 httplib::Server::listen_internal()+920

stop() closes the listening socket itself, and listen_internal() closes
it again on the way out. In between the number is free, and in a jvm
process something always takes it - art had opened the apk and got 101.
fdsan catches the second close and aborts; without fdsan it would quietly
close an unrelated file.

So the loader stops tearing down. The server is created on the first
initialize() and never stopped: close() calls clear(), which drops the
documents this loader published and leaves the socket listening, and the
process exit reclaims the rest. That was already what happened - nothing
here ever outlived its process - and it is what makes both races
unreachable, since both need a stop() or a destroy that no longer comes.

Smaller as a result: no join, no timeout, no thread to wait on the
listening one, and no blocking work left in close() at all, which
LoaderService.onDestroy runs on the main thread. bind()'s fallback also
stops being reached by our own teardown, because the port is now bound
once rather than once per service lifetime.

Reported upstream as opendocument-app/OpenDocument.core#631. Until it is
fixed, bind and listen once, clear() between documents, never stop().

Both flavors, five runs each, 25 tests, green, and no tombstone written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eTZB8rnh9PrdNn1YZexue
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.

1 participant