From ba61904d129004dbaaadc6d6bd270b7e44410688 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 10 Aug 2026 21:06:09 +0200 Subject: [PATCH 1/4] Drop the raw and online loaders odrcore 6.5 renders svg and xml itself, which is the whole of what RawLoader existed for, so the routing around the core goes with it - along with nameSays, which only guarded that routing. OnlineLoader goes too. It was the last thing that sent a document off the device, to use.opendocument.app or to google's and microsoft's viewers, and what it still reached was rtf and WordPerfect. Those are the engine's to learn. A file the core cannot open now says so instead of offering an upload. That leaves MetadataLoader and CoreLoader, so the FileLoader base, LoaderService and LoaderServiceQueue stay: metadata still has to cache, name and type a file before the core sees it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CAJTcj3dfmn1yC5TpqdCeb --- CHANGELOG.md | 12 + CLAUDE.md | 45 +- .../droid/background/CoreLoaderTest.kt | 17 +- .../droid/background/OnlineLoaderTest.kt | 104 --- .../droid/background/RawLoaderTest.kt | 100 --- .../opendocument/droid/test/LandingTests.kt | 6 +- .../droid/test/MainActivityTests.kt | 7 +- .../droid/test/SupportedFormatsTest.kt | 15 +- app/src/main/assets/image.html | 722 ------------------ .../droid/background/FileLoader.kt | 8 +- .../droid/background/LoaderService.kt | 52 +- .../droid/background/MetadataLoader.kt | 2 +- .../droid/background/OnlineLoader.kt | 270 ------- .../droid/background/RawLoader.kt | 68 -- .../background/SupportedDocumentTypes.kt | 43 +- .../droid/ui/activity/DocumentFragment.kt | 94 +-- .../droid/ui/activity/MainActivity.kt | 2 +- .../opendocument/droid/ui/widget/PageView.kt | 11 +- .../droid/ui/widget/ProgressDialogFragment.kt | 19 +- app/src/main/res/values-ca-rES/strings.xml | 3 - app/src/main/res/values-ca/strings.xml | 3 - app/src/main/res/values-cs-rCZ/strings.xml | 3 - app/src/main/res/values-cs/strings.xml | 3 - app/src/main/res/values-da-rDK/strings.xml | 3 - app/src/main/res/values-da/strings.xml | 3 - app/src/main/res/values-de-rDE/strings.xml | 4 - app/src/main/res/values-de/strings.xml | 4 - app/src/main/res/values-es-rES/strings.xml | 4 - app/src/main/res/values-es/strings.xml | 4 - app/src/main/res/values-fr-rFR/strings.xml | 4 - app/src/main/res/values-fr/strings.xml | 4 - app/src/main/res/values-ga-rIE/strings.xml | 4 - app/src/main/res/values-ga/strings.xml | 4 - app/src/main/res/values-hi-rIN/strings.xml | 2 - app/src/main/res/values-it-rIT/strings.xml | 4 - app/src/main/res/values-it/strings.xml | 3 - app/src/main/res/values-ja-rJP/strings.xml | 4 - app/src/main/res/values-ja/strings.xml | 4 - app/src/main/res/values-ko-rKR/strings.xml | 4 - app/src/main/res/values-pl-rPL/strings.xml | 3 - app/src/main/res/values-pl/strings.xml | 3 - app/src/main/res/values-pt-rBR/strings.xml | 4 - app/src/main/res/values-ru-rRU/strings.xml | 3 - app/src/main/res/values-ru/strings.xml | 3 - app/src/main/res/values-sl-rSI/strings.xml | 4 - app/src/main/res/values-sl/strings.xml | 4 - app/src/main/res/values-tr-rTR/strings.xml | 4 - app/src/main/res/values-tr/strings.xml | 4 - app/src/main/res/values-zh-rCN/strings.xml | 4 - app/src/main/res/values-zh/strings.xml | 4 - app/src/main/res/values/strings.xml | 6 - gradle/libs.versions.toml | 2 +- 52 files changed, 93 insertions(+), 1623 deletions(-) delete mode 100644 app/src/androidTest/java/app/opendocument/droid/background/OnlineLoaderTest.kt delete mode 100644 app/src/androidTest/java/app/opendocument/droid/background/RawLoaderTest.kt delete mode 100644 app/src/main/assets/image.html delete mode 100644 app/src/main/java/app/opendocument/droid/background/OnlineLoader.kt delete mode 100644 app/src/main/java/app/opendocument/droid/background/RawLoader.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0a43df7333..3600dcb5199e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,18 @@ Play Console when the release is promoted. - Opening an archive lists its entries as files instead of one page of gibberish. - Embedded fonts in a PDF no longer come out as boxes or as the wrong letters. - svg, ico, jxl, jp2, psd, wmf and emf are recognised and shown as images. +- An .xml file opens as a foldable, highlighted source view, in the encoding its + declaration names, instead of as one very long line. An .svg is recognised by + what is in it rather than by what it is called. Both used to be handed to the + browser view as they were. +- Plain text reads in a quieter gutter: the line numbers line up with their + lines and stay out of a copy of the page. +- No document opens inset by a thin border any more. +- A file the app cannot open is no longer offered for upload to our conversion + service. Nothing about a document leaves the device any more: the reader shows + what the engine renders and says plainly when it cannot. .rtf and WordPerfect + were the formats this still reached; they come back when the engine reads them + itself. ## 4.13.0 diff --git a/CLAUDE.md b/CLAUDE.md index 47ba13946b18..b2d3017efb60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,9 +16,9 @@ Guidance for Claude Code (claude.ai/code) working in this repository. A `FileLoader` loads on `LoaderService`'s background thread and reports through `FileLoaderListener`; `LoaderServiceQueue` holds requests until the service is bound. -`MetadataLoader` caches and identifies the file, `RawLoader` takes svg and xml, -`CoreLoader` renders the rest odrcore handles and publishes the html on a local server, and -`OnlineLoader` uploads to a web viewer what neither can open. +`MetadataLoader` caches and identifies the file and `CoreLoader` renders it, publishing the +html on a local server. Those two are the whole chain - there is no fallback after the core, +and what it cannot open is reported as an unsupported format. `MainActivity` owns the service binding and the action modes (find, tts, edit), and swaps between `LandingFragment` (recent documents and settings) and `DocumentFragment`, which @@ -143,27 +143,27 @@ XML cannot read any of that, so the `STRICT_CATCH` alias' three intent-filters a *generated* from the same table - a filter matches a mime type exactly, so all 49 spellings and 41 extensions are written out. `SupportedFormatsTest` asserts that `SupportedDocumentTypes` and the package manager agree, and that every claimed mime type -reaches a loader, so a format added upstream and forgotten fails CI. +reaches `CoreLoader`, so a format added upstream and forgotten fails CI. -The tables live in `libodr_jni`, which is why `CoreLoaderTest`, -`SupportedDocumentTypesTest`, `RawLoaderTest` and `OnlineLoaderTest` are instrumented though -none opens a file. After caching it is `Odr.mimetype` that decides, canonicalized through -`canonicalMimeType` so the loaders see one spelling per format. +The tables live in `libodr_jni`, which is why `CoreLoaderTest` and +`SupportedDocumentTypesTest` are instrumented though neither opens a file. After caching it +is `Odr.mimetype` that decides, canonicalized through `canonicalMimeType` so the loaders see +one spelling per format. Reading the core's table directly, as `isDocument` does, must not `lowercase()` first: it matches exactly and spells some types with capitals (`macroEnabled`). Our own sets are the other way round - `mimeTypesOf` lowercases what it stores. -### `RawLoader` is asked before `CoreLoader`, not after it +### There is nothing after `CoreLoader` -`LoaderService.onSuccess` asks `rawLoader.isSupported` *first*, because the core would -render an svg itself. `isRenderedByRaw` is the whole list: svg, which the WebView draws -just as well from the file, and xml, which the core names without a decoder. A `RawLoader` -failure falls through to the core; what the core cannot open goes to the upload offer -rather than to the WebView on spec. +The app used to route around the core twice: `RawLoader` handed svg and xml straight to the +WebView, and `OnlineLoader` uploaded to use.opendocument.app or a third party viewer what +neither could open. odrcore 6.5 renders svg and xml itself, and both are gone. -Routing by name is guarded - see `nameSays`. The core identifies by content, so a -`drawing.svg` holding an odt is an odt. +So the only answer to a file the core cannot open is to say so - `onUnsupported`, the reopen +bar and the contact dialog. Do not add a route around the core back: a format the app should +open is a format odrcore should learn, and rtf and WordPerfect are on that list. Nothing in +the app makes an outbound request for a document any more, and no document leaves the device. ### `text/plain` from the core is a guess unless a charset came with it @@ -173,8 +173,8 @@ rendered, on the server thread, long after `CoreLoader` reported success. So `MetadataLoader` drops a `text/plain` whose file has no charset (`hasKnownCharset`) and lets the fallbacks below it decide, and `CoreLoader.host()` refuses the same file up front. -Both are needed: the first keeps `isRenderedByCore` and `OnlineLoader`'s `"text/"` whitelist -off a `.bin`, the second stops a success bar appearing over a page that cannot draw. +Both are needed: the first keeps `isRenderedByCore` off a `.bin`, the second stops a success +bar appearing over a page that cannot draw. `LandingTests.aDocumentThatFailsToOpenComesBackToTheList` holds this. ### Editability comes from the core, never from a mime type @@ -200,13 +200,14 @@ recent list rather than releasing on close. Do not add a release next to `documentFragment.loadUri()`: that call only queues the load, so the stream is opened long after it returns. -### Kotlin, and the three `@Jvm` annotations left +### Kotlin, and the two `@Jvm` annotations left The only java is `com/commonsware/android/print`, vendored so it can be diffed against upstream. It calls nothing of ours, so no java-to-kotlin call exists and `@JvmStatic`, `@JvmField`, `@JvmOverloads` and `@Throws` are not needed for interop. What remains is for runtimes that reflect over the bytecode: `@JvmField` on `FileLoader`'s -`CREATOR`s (parcelable needs a static field), `@JvmOverloads` on `ProgressDialogFragment`'s -constructor (the framework re-creates it with no arguments), and `@JvmStatic` on -`@BeforeClass` / `@AfterClass` in the instrumented tests. +`CREATOR`s (parcelable needs a static field) and `@JvmStatic` on `@BeforeClass` / +`@AfterClass` in the instrumented tests. `ProgressDialogFragment` needed `@JvmOverloads` too +while it took an argument - a fragment the framework re-creates has to have a no-arg +constructor. diff --git a/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt b/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt index ddf54326b17f..3c75dfa4faee 100644 --- a/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt +++ b/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt @@ -99,15 +99,13 @@ class CoreLoaderTest { // rendered when handed one, never claimed in the share sheet assertTrue(isSupported("audio/mpeg")) assertTrue(isSupported("video/mp4")) - // the image types 6.3 added, svg among them - [RawLoader] takes that one anyway + // the image types 6.3 added assertTrue(isSupported("image/svg+xml")) assertTrue(isSupported("image/jxl")) assertTrue(isSupported("image/vnd.adobe.photoshop")) } - /** - * 6.4 opens a csv as a spreadsheet, so the table is the core's to draw now, not RawLoader's. - */ + /** 6.4 opens a csv as a spreadsheet, so the table is the core's to draw. */ @Test fun csvIsSupported() { assertTrue(isSupported("text/csv")) @@ -115,11 +113,14 @@ class CoreLoaderTest { assertTrue(isSupported("text/comma-separated-values")) } - /** Named by the core since 6.3, but with no decoder behind the name - still [RawLoader]'s. */ + /** + * 6.5 puts a decoder behind the name it gave xml in 6.3, which is what let the raw loader go: + * svg above and xml here were the two formats it existed for. + */ @Test - fun whatTheCoreNamesButCannotTranslateIsNotClaimed() { - assertFalse(isSupported("application/xml")) - assertFalse(isSupported("text/xml")) + fun xmlIsSupported() { + assertTrue(isSupported("application/xml")) + assertTrue(isSupported("text/xml")) } @Test diff --git a/app/src/androidTest/java/app/opendocument/droid/background/OnlineLoaderTest.kt b/app/src/androidTest/java/app/opendocument/droid/background/OnlineLoaderTest.kt deleted file mode 100644 index 695c1aefa266..000000000000 --- a/app/src/androidTest/java/app/opendocument/droid/background/OnlineLoaderTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package app.opendocument.droid.background - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented since odrcore 6.1: [OnlineLoader.isConvertible] falls through to - * [SupportedDocumentTypes.isDocument], which reads the core's table in `libodr_jni`. Nothing here - * uploads anything or touches the network. - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -class OnlineLoaderTest { - - private lateinit var onlineLoader: OnlineLoader - - @Before - fun setUp() { - onlineLoader = OnlineLoader(null) - } - - private fun options(fileType: String): FileLoader.Options { - val options = FileLoader.Options() - options.fileType = fileType - return options - } - - private fun isSupported(fileType: String): Boolean = onlineLoader.isSupported(options(fileType)) - - private fun isConvertible(fileType: String): Boolean = - onlineLoader.isConvertible(options(fileType)) - - @Test - fun supportsOfficeDocuments() { - Assert.assertTrue(isSupported("application/vnd.oasis.opendocument.text")) - Assert.assertTrue( - isSupported("application/vnd.openxmlformats-officedocument.wordprocessingml.document") - ) - Assert.assertTrue(isSupported("application/msword")) - Assert.assertTrue(isSupported("application/vnd.ms-excel")) - Assert.assertTrue(isSupported("application/vnd.ms-powerpoint")) - Assert.assertTrue(isSupported("application/pdf")) - Assert.assertTrue(isSupported("application/rtf")) - Assert.assertTrue(isSupported("application/vnd.wordperfect")) - } - - @Test - fun supportsGenericPreviewableTypes() { - Assert.assertTrue(isSupported("text/plain")) - Assert.assertTrue(isSupported("image/png")) - Assert.assertTrue(isSupported("application/zip")) - } - - @Test - fun rejectsBlacklistedMimeTypes() { - Assert.assertFalse(isSupported("image/x-tga")) - Assert.assertFalse(isSupported("image/vnd.djvu")) - Assert.assertFalse(isSupported("audio/amr")) - Assert.assertFalse(isSupported("video/3gpp")) - Assert.assertFalse(isSupported("text/calendar")) - // contacts must not be offered for upload - see issue #477 - Assert.assertFalse(isSupported("text/vcard")) - } - - @Test - fun rejectsUnknownMimeTypes() { - Assert.assertFalse(isSupported("application/octet-stream")) - Assert.assertFalse(isSupported("application/x-made-up")) - } - - @Test - fun convertsOfficeDocumentsItself() { - Assert.assertTrue(isConvertible("application/pdf")) - Assert.assertTrue(isConvertible("text/rtf")) - Assert.assertTrue(isConvertible("application/vnd.wordperfect")) - Assert.assertTrue(isConvertible("application/vnd.ms-excel")) - Assert.assertTrue(isConvertible("application/msword")) - Assert.assertTrue(isConvertible("application/vnd.ms-powerpoint")) - Assert.assertTrue( - isConvertible( - "application/vnd.openxmlformats-officedocument.presentationml.presentation" - ) - ) - // whatever else the core files as a document - Assert.assertTrue(isConvertible("application/vnd.oasis.opendocument.text")) - // including what it cannot open - the converter runs libreoffice, not odrcore - Assert.assertTrue(isConvertible("application/vnd.ms-excel.sheet.binary.macroEnabled.12")) - } - - /** The categories a converter cannot help with. */ - @Test - fun handsEverythingElseToAThirdPartyViewer() { - Assert.assertFalse(isConvertible("text/plain")) - Assert.assertFalse(isConvertible("image/png")) - Assert.assertFalse(isConvertible("application/zip")) - Assert.assertFalse(isConvertible("audio/mpeg")) - Assert.assertFalse(isConvertible("video/mp4")) - Assert.assertFalse(isConvertible("application/vnd.apple.pages")) - } -} diff --git a/app/src/androidTest/java/app/opendocument/droid/background/RawLoaderTest.kt b/app/src/androidTest/java/app/opendocument/droid/background/RawLoaderTest.kt deleted file mode 100644 index b9d0ba9c097d..000000000000 --- a/app/src/androidTest/java/app/opendocument/droid/background/RawLoaderTest.kt +++ /dev/null @@ -1,100 +0,0 @@ -package app.opendocument.droid.background - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -/** - * The two things [RawLoader] still has a viewer for, and the many it handed back to the core. - * - * Instrumented because the mime type spellings come from odrcore's table in `libodr_jni`. - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -class RawLoaderTest { - - private lateinit var rawLoader: RawLoader - - @Before - fun setUp() { - // no context: isSupported() is pure, and constructing a loader has no side effects - rawLoader = RawLoader(null) - } - - private fun isSupported(fileType: String?, filename: String? = null): Boolean { - val options = FileLoader.Options() - options.fileType = fileType - options.filename = filename - - return rawLoader.isSupported(options) - } - - /** - * How svg and xml actually arrive: the core identifies by content and an svg *is* text, so - * `Odr.mimetype` says `text/plain` and only the name knows better. - */ - @Test - fun whatTheCoreMisreadsAsTextIsRoutedByItsName() { - assertTrue(isSupported("text/plain", "drawing.svg")) - assertTrue(isSupported("text/plain", "feed.xml")) - // and a text file is still a text file - assertFalse(isSupported("text/plain", "readme.txt")) - assertFalse(isSupported("text/plain", "notes")) - } - - /** - * The other half: the bytes win whenever the core recognized them. An odt called `drawing.svg` - * would otherwise reach the image viewer, succeed, and leave no fallback. - */ - @Test - fun aMisnamedDocumentIsStillADocument() { - assertFalse(isSupported("application/vnd.oasis.opendocument.text", "report.xml")) - assertFalse(isSupported("application/pdf", "report.xml")) - assertFalse(isSupported("application/zip", "archive.svg")) - assertFalse(isSupported("image/png", "scan.xml")) - // but nothing detected at all still leaves the name as the only thing to go on - assertTrue(isSupported("application/octet-stream", "feed.xml")) - assertTrue(isSupported(null, "drawing.svg")) - } - - /** The WebView draws both, so nothing is gained by translating them first. */ - @Test - fun whatTheWebViewDrawsItselfIsSupported() { - assertTrue(isSupported("image/svg+xml")) - assertTrue(isSupported("application/xml")) - assertTrue(isSupported("text/xml")) - } - - /** Each had a viewer here until odrcore learned to render it. Keeps them from coming back. */ - @Test - fun whatTheCoreRendersIsLeftToIt() { - assertFalse(isSupported("text/plain")) - assertFalse(isSupported("image/png")) - assertFalse(isSupported("image/webp")) - assertFalse(isSupported("application/zip")) - assertFalse(isSupported("audio/mpeg")) - assertFalse(isSupported("video/mp4")) - assertFalse(isSupported("application/json")) - assertFalse(isSupported("text/csv")) - assertFalse(isSupported("application/csv")) - assertFalse(isSupported("text/comma-separated-values")) - // and by name, not only by mime type - assertFalse(isSupported("text/plain", "rows.csv")) - assertFalse(isSupported("application/octet-stream", "rows.csv")) - } - - /** No rename-and-hope for these - the upload offer beats a blank page. */ - @Test - fun whatNobodyCanShowIsNotSupported() { - assertFalse(isSupported("application/pdf")) - assertFalse(isSupported("application/vnd.oasis.opendocument.text")) - assertFalse(isSupported("application/octet-stream")) - assertFalse(isSupported("text/vcard")) - assertFalse(isSupported("text/rtf")) - assertFalse(isSupported(null)) - } -} diff --git a/app/src/androidTest/java/app/opendocument/droid/test/LandingTests.kt b/app/src/androidTest/java/app/opendocument/droid/test/LandingTests.kt index fba2337059a0..274b15bb66af 100644 --- a/app/src/androidTest/java/app/opendocument/droid/test/LandingTests.kt +++ b/app/src/androidTest/java/app/opendocument/droid/test/LandingTests.kt @@ -380,9 +380,9 @@ class LandingTests { * * The extension is part of the fixture. `Odr.mimetype` cannot identify these bytes, so * `MetadataLoader` falls back to what the provider makes of the filename - and that type is - * what decides which failure the user gets. `.bin` gives `application/octet-stream`, which no - * loader claims, so the app runs out of things to try. Name it `.odt` and the upload offer - * takes over instead, because `OnlineLoader` whitelists the type the name implies. + * what decides which failure the user gets. `.bin` gives `application/octet-stream`, which the + * core does not claim, so the file is reported as an unsupported format. Name it `.odt` and the + * core claims the format and fails on the bytes, which is the broken-file dialog instead. */ private fun seedBrokenDocument() { val broken = File(requireTestFile().parentFile, BROKEN_DOCUMENT) diff --git a/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt b/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt index 4e645740ff0f..70673d468de2 100644 --- a/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt +++ b/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt @@ -163,15 +163,14 @@ class MainActivityTests { } @Test - fun testCorruptODTIsNotOfferedForUpload() { + fun testCorruptODTOffersContact() { val activity = mainActivityActivityTestRule.activity - // the core claims the format and fails on the file, which is final - an upload runs the - // same core. not loadDocument(), which waits for a fragment this path takes back down + // the core claims the format and fails on the file, which is final. not loadDocument(), + // which waits for a fragment this path takes back down val testFileUri = uriOf(requireTestFile("corrupt.odt")) InstrumentationRegistry.getInstrumentation().runOnMainSync { activity.loadUri(testFileUri) } - onView(withText(R.string.action_upload)).check(doesNotExist()) onView(withText(R.string.dialog_broken_file)).check(matches(isDisplayed())) onView(withText(R.string.action_contact)).check(matches(isDisplayed())) } diff --git a/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt b/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt index a83c513ecb94..f312e0f054e2 100644 --- a/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt +++ b/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt @@ -10,7 +10,6 @@ import app.opendocument.core.Odr import app.opendocument.droid.background.CatchAllSetting import app.opendocument.droid.background.CoreLoader import app.opendocument.droid.background.FileLoader -import app.opendocument.droid.background.RawLoader import app.opendocument.droid.background.SupportedDocumentTypes import org.junit.Assert import org.junit.BeforeClass @@ -85,25 +84,25 @@ class SupportedFormatsTest { } /** - * Everything the app offers itself for reaches a loader that takes it. + * Everything the app offers itself for reaches [CoreLoader], which is the only loader that + * renders anything. * * Claiming a mime type nobody loads is the "cannot open" the user gets on a file they picked us - * for. [CoreLoader] takes all of it that the core names; what is left for [RawLoader] is svg - * and xml, which [LoaderService] therefore routes before the core. + * for. The app claims from the core's own table, so this asks that the two ways of reading that + * table - `CLAIMED_FILE_TYPES` and `CORE_FILE_TYPES` - cannot come apart. */ @Test - fun everythingTheAppClaimsIsLoadedBySomebody() { + fun everythingTheAppClaimsIsLoadedByTheCore() { val coreLoader = CoreLoader(null) - val rawLoader = RawLoader(null) for (mimeType in SupportedDocumentTypes.MIME_TYPES) { val options = FileLoader.Options() options.fileType = SupportedDocumentTypes.canonicalMimeType(mimeType) Assert.assertTrue( - "$mimeType is claimed by the app, but neither loader takes it" + + "$mimeType is claimed by the app, but the core loader does not take it" + " (as ${options.fileType})", - coreLoader.isSupported(options) || rawLoader.isSupported(options), + coreLoader.isSupported(options), ) } } diff --git a/app/src/main/assets/image.html b/app/src/main/assets/image.html deleted file mode 100644 index a08f87892fc6..000000000000 --- a/app/src/main/assets/image.html +++ /dev/null @@ -1,722 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/app/opendocument/droid/background/FileLoader.kt b/app/src/main/java/app/opendocument/droid/background/FileLoader.kt index 4cb8a6e5523e..06e3ff7b1975 100644 --- a/app/src/main/java/app/opendocument/droid/background/FileLoader.kt +++ b/app/src/main/java/app/opendocument/droid/background/FileLoader.kt @@ -12,7 +12,7 @@ import java.io.File import java.util.LinkedList /** - * Base of the four loaders: each one turns a document into something the WebView can display and + * Base of the two loaders: each one turns a document into something the WebView can display and * reports back on the main thread through a [FileLoaderListener]. * * The lifecycle is construct - [initialize] - [loadAsync]* - [close]. Constructing is deliberately @@ -118,8 +118,6 @@ abstract class FileLoader(context: Context?, protected val type: LoaderType) { enum class LoaderType { CORE, - ONLINE, - RAW, METADATA, } @@ -196,8 +194,8 @@ abstract class FileLoader(context: Context?, protected val type: LoaderType) { /** * Whether the loaded document can be edited and saved again, as the core reports it - see - * [CoreLoader.isDocumentEditable]. False for every other loader, which have nothing to - * edit. + * [CoreLoader.isDocumentEditable]. False for a [MetadataLoader] result, which has nothing + * to edit. */ var isEditable: Boolean = false diff --git a/app/src/main/java/app/opendocument/droid/background/LoaderService.kt b/app/src/main/java/app/opendocument/droid/background/LoaderService.kt index ff3bdef97705..52a3af437209 100644 --- a/app/src/main/java/app/opendocument/droid/background/LoaderService.kt +++ b/app/src/main/java/app/opendocument/droid/background/LoaderService.kt @@ -15,12 +15,11 @@ import app.opendocument.droid.ui.activity.DocumentFragment import java.io.File /** - * Owns the four loaders and the background thread they run on, and decides what to try next when - * one of them succeeds or fails: metadata first, then raw or the core, and finally an upload the - * user has to agree to. + * Owns the two loaders and the background thread they run on, and decides what to try next when one + * of them succeeds or fails: metadata first, then the core. * - * [RawLoader] sits outside that chain rather than at the end of it: the core would succeed at an - * svg, so it has to be asked first. + * There is nothing after the core: what it cannot open is reported as unsupported rather than + * routed somewhere else. */ class LoaderService : Service(), FileLoader.FileLoaderListener { @@ -34,8 +33,6 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { private lateinit var metadataLoader: MetadataLoader private lateinit var coreLoader: CoreLoader - private lateinit var rawLoader: RawLoader - private lateinit var onlineLoader: OnlineLoader private var currentListener: LoaderListener? = null @@ -67,18 +64,6 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { coreLoader = CoreLoader(this) coreLoader.initialize(this, mainHandler, backgroundHandler, analyticsManager, crashManager) - - rawLoader = RawLoader(this) - rawLoader.initialize(this, mainHandler, backgroundHandler, analyticsManager, crashManager) - - onlineLoader = OnlineLoader(this) - onlineLoader.initialize( - this, - mainHandler, - backgroundHandler, - analyticsManager, - crashManager, - ) } override fun onBind(intent: Intent?): IBinder = LoaderBinder() @@ -97,8 +82,6 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { val loader = when (loaderType) { FileLoader.LoaderType.CORE -> coreLoader - FileLoader.LoaderType.ONLINE -> onlineLoader - FileLoader.LoaderType.RAW -> rawLoader FileLoader.LoaderType.METADATA -> metadataLoader } @@ -108,13 +91,6 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { override fun onSuccess(result: FileLoader.Result) { val options = result.options if (result.loaderType == FileLoader.LoaderType.METADATA) { - // first, not last: the core would render an svg itself and RawLoader would never run - if (rawLoader.isSupported(options)) { - loadWithType(FileLoader.LoaderType.RAW, options) - - return - } - if (!coreLoader.isSupported(options)) { crashManager.log("we do not expect this file to be an ODF: " + options.originalUri) analyticsManager.report( @@ -160,27 +136,17 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { ) if (coreLoader.isSupported(options)) { - // the core names the format and still said no, so the file is what is wrong - - // an upload would only run the same engine again + // the core names the format and still said no, so the file is what is wrong, + // not the format withListener { it.onError(result, error) } } else { withListener { it.onUnsupported(result) } } - return - } else if (result.loaderType == FileLoader.LoaderType.RAW) { - // the core has not had its turn yet. not gated on isSupported, which says nothing - // about xml; if it fails too, the branch above reports that properly - loadWithType(FileLoader.LoaderType.CORE, options) - - return - } else if (result.loaderType != FileLoader.LoaderType.METADATA) { - withListener { it.onError(result, error) } - return } - // MetadataLoader failed, so there's no point in trying to parse or upload the file + // MetadataLoader failed, so there's no point in trying to parse the file analyticsManager.report( "load_error", @@ -193,8 +159,6 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { withListener { it.onError(result, error) } } - fun isOnlineSupported(options: FileLoader.Options): Boolean = onlineLoader.isSupported(options) - fun saveAsync(lastResult: FileLoader.Result, outFile: Uri, htmlDiff: String?) { backgroundHandler.post { saveSync(lastResult, outFile, htmlDiff) } } @@ -340,8 +304,6 @@ class LoaderService : Service(), FileLoader.FileLoaderListener { override fun onDestroy() { metadataLoader.close() coreLoader.close() - rawLoader.close() - onlineLoader.close() // quitSafely, not quit: close() posts each loader's teardown, which quit() would drop backgroundThread.quitSafely() diff --git a/app/src/main/java/app/opendocument/droid/background/MetadataLoader.kt b/app/src/main/java/app/opendocument/droid/background/MetadataLoader.kt index 36b09ac43e93..0b61675aefa7 100644 --- a/app/src/main/java/app/opendocument/droid/background/MetadataLoader.kt +++ b/app/src/main/java/app/opendocument/droid/background/MetadataLoader.kt @@ -83,7 +83,7 @@ class MetadataLoader(context: Context?) : FileLoader(context, LoaderType.METADAT // text/plain without a charset is the core's fallback for bytes nothing else // claims, not an identification - it would hand random binary to the text - // renderer, and to the upload offer through its "text/" whitelist + // renderer if (mimetype == TEXT_MIME_TYPE && !hasKnownCharset(cachedFile)) { mimetype = null } diff --git a/app/src/main/java/app/opendocument/droid/background/OnlineLoader.kt b/app/src/main/java/app/opendocument/droid/background/OnlineLoader.kt deleted file mode 100644 index cefff77b1d1f..000000000000 --- a/app/src/main/java/app/opendocument/droid/background/OnlineLoader.kt +++ /dev/null @@ -1,270 +0,0 @@ -package app.opendocument.droid.background - -import android.content.Context -import android.net.Uri -import java.io.IOException -import java.io.OutputStreamWriter -import java.io.PrintWriter -import java.net.HttpURLConnection -import java.net.URL -import java.net.URLEncoder - -/** - * Last resort for documents nothing on the device can render: either use.opendocument.app converts - * them, or they are uploaded to transfer.opendocument.app and shown in a third party viewer. - */ -class OnlineLoader(context: Context?) : FileLoader(context, LoaderType.ONLINE) { - - override fun isSupported(options: Options): Boolean { - val fileType = options.fileType ?: return false - - return MIME_WHITELIST.any { fileType.startsWith(it) } && - MIME_BLACKLIST.none { fileType.startsWith(it) } - } - - override fun loadSync(options: Options) { - val result = Result(type, options) - - try { - val viewerUri = - if (isConvertible(options)) doOnlineConvert(options) else doTransferUpload(options) - - result.partTitles.add(null) - result.partUris.add(viewerUri) - - callOnSuccess(result) - } catch (e: Throwable) { - callOnError(result, e) - } - } - - /** - * Whether use.opendocument.app converts this itself; everything else is uploaded and handed to - * a third party viewer. - * - * The last term asks whether the core *files* this as a document, not whether it renders one: - * the converter runs libreoffice, so an `.xlsb` is worth sending it. - */ - fun isConvertible(options: Options): Boolean { - val fileType = options.fileType - - return "text/rtf" == fileType || - "application/vnd.wordperfect" == fileType || - "application/vnd.ms-excel" == fileType || - "application/msword" == fileType || - "application/vnd.ms-powerpoint" == fileType || - "application/pdf" == fileType || - fileType?.startsWith("application/vnd.openxmlformats-officedocument.") == true || - SupportedDocumentTypes.isDocument(fileType) - } - - private fun doOnlineConvert(options: Options): Uri { - // https://stackoverflow.com/a/2469587/198996 - val basePath = "https://use.opendocument.app" - val url = "$basePath/upload" // TODO: /v1 - val binaryFile = - checkNotNull(AndroidFileCache.getCacheFile(context, checkNotNull(options.cacheUri))) - val boundary = System.currentTimeMillis().toString(16) - val crlf = "\r\n" - val disposition = "Content-Disposition: form-data; name=\"document\"; filename=\"document\"" - - val connection = URL(url).openConnection() as HttpURLConnection - connection.requestMethod = "POST" - connection.doOutput = true - connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=$boundary") - connection.instanceFollowRedirects = false - - try { - connection.outputStream.use { output -> - PrintWriter(OutputStreamWriter(output, StreamUtil.ENCODING), true).use { writer -> - writer.append("--$boundary").append(crlf) - writer.append(disposition).append(crlf) - writer.append(crlf).flush() - StreamUtil.copy(binaryFile, output) - output.flush() - writer.append(crlf).flush() - - writer.append("--$boundary--").append(crlf).flush() - } - } - - // a converted document is answered with a redirect to it, so a response without a - // Location is the server refusing the file - concatenating the missing header would - // build a "...appnull" url that only fails later, inside the webview - val responseCode = connection.responseCode - val redirectUrl = connection.getHeaderField("Location") - if (redirectUrl.isNullOrEmpty()) { - val error = readError(connection) - throw IOException("server couldn't handle request: $responseCode $error") - } - - return Uri.parse(basePath + redirectUrl) - } finally { - // the success path never reads the body, so the socket would sit in the keep-alive - // pool with an unconsumed response until it timed out - connection.disconnect() - } - } - - private fun doTransferUpload(options: Options): Uri { - val binaryFile = - checkNotNull(AndroidFileCache.getCacheFile(context, checkNotNull(options.cacheUri))) - val encodedFilename = URLEncoder.encode(options.filename, StreamUtil.ENCODING) - - val connection = - URL(TRANSFER_BASE_URL + encodedFilename).openConnection() as HttpURLConnection - connection.requestMethod = "PUT" - connection.doOutput = true - connection.instanceFollowRedirects = false - - connection.outputStream.use { outputStream -> - StreamUtil.copy(binaryFile, outputStream) - outputStream.flush() - } - - val responseCode = connection.responseCode - if (responseCode in 200..299) { - val downloadUrl = readBody(connection) - if (downloadUrl.isNullOrEmpty()) { - throw IOException("server couldn't handle request") - } - - return buildViewerUri(options, downloadUrl.trim()) - } else { - val error = readError(connection) - throw IOException("server couldn't handle request: $responseCode $error") - } - } - - private fun buildViewerUri(options: Options, downloadUrl: String): Uri { - // google's viewer will not take odf, microsoft's will not take pdf - val isPdf = options.fileType?.startsWith("application/pdf") == true - - // the office viewer wants an office document; an image or an mp3 is google's problem - if (SupportedDocumentTypes.isDocument(options.fileType) && !isPdf) { - return Uri.parse(MICROSOFT_VIEWER_URL + downloadUrl) - } - - return Uri.parse(GOOGLE_VIEWER_URL + URLEncoder.encode(downloadUrl, StreamUtil.ENCODING)) - } - - private fun readBody(connection: HttpURLConnection): String? { - val inputStream = connection.inputStream ?: return null - - return StreamUtil.readFully(inputStream) - } - - private fun readError(connection: HttpURLConnection): String? { - try { - val errorStream = connection.errorStream ?: return null - - return StreamUtil.readFully(errorStream) - } catch (t: Throwable) { - return null - } - } - - companion object { - private const val TRANSFER_BASE_URL = "https://transfer.opendocument.app/" - - const val GOOGLE_VIEWER_URL: String = "https://docs.google.com/viewer?embedded=true&url=" - const val MICROSOFT_VIEWER_URL: String = - "https://view.officeapps.live.com/op/view.aspx?src=" - - // https://help.joomlatools.com/article/169-google-viewer - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types - // - // matched as prefixes, so the four families at the top already cover every "text/..." and - // "image/..." spelling of the formats below them - private val MIME_WHITELIST = - arrayOf( - "text/", - "image/", - "video/", - "audio/", - // markup - "application/json", - "application/xml", - "application/css-stylesheet", - "application/xhtml", - "application/x-httpd-php", - "application/php", - "application/x-php", - "application/x-javascript", - "application/ms-java", - // rtf - "application/rtf", - // psd: https://filext.com/file-extension/PSD - "application/photoshop", - "application/psd", - "zz-application/zz-winassoc-psd", - // pdf: https://filext.com/file-extension/PDF - "application/pdf", - "application/x-pdf", - "application/acrobat", - "applications/vnd.pdf", - // odf: https://filext.com/file-extension/ODT - "application/vnd.oasis.opendocument", - "application/x-vnd.oasis.opendocument", - // ms - "application/vnd.openxmlformats-officedocument", - // doc: https://filext.com/file-extension/DOC - "application/msword", - "application/doc", - "appl/text", - "application/vnd.msword", - "application/vnd.ms-word", - "application/winword", - "application/word", - "application/x-msw6", - "application/x-msword", - // xls: https://filext.com/file-extension/XLS - "application/vnd.ms-excel", - "application/msexcel", - "application/x-msexcel", - "application/x-ms-excel", - "application/x-excel", - "application/x-dos_ms_excel", - "application/xls", - // ppt: https://filext.com/file-extension/PPT - "application/vnd.ms-powerpoint", - "application/mspowerpoint", - "application/ms-powerpoint", - "application/mspowerpnt", - "application/vnd-mspowerpoint", - "application/powerpoint", - "application/x-powerpoint", - // apple - "application/x-iwork", - "application/vnd.apple", - // postscript: https://filext.com/file-extension/EPS - "application/postscript", - "application/eps", - "application/x-eps", - // autocad: https://filext.com/file-extension/DXF - "application/dxf", - "application/x-autocad", - "application/x-dxf", - "drawing/x-dxf", - "zz-application/zz-winassoc-dxf", - // zip: https://filext.com/file-extension/ZIP - "application/zip", - "application/x-zip", - "application/x-compress", - "multipart/x-zip", - // WPD - "application/vnd.wordperfect", - ) - - private val MIME_BLACKLIST = - arrayOf( - "image/x-tga", - "image/vnd.djvu", - "image/g3fax", - "audio/amr", - "text/calendar", - "text/vcard", - "video/3gpp", - ) - } -} diff --git a/app/src/main/java/app/opendocument/droid/background/RawLoader.kt b/app/src/main/java/app/opendocument/droid/background/RawLoader.kt deleted file mode 100644 index d5e88204af50..000000000000 --- a/app/src/main/java/app/opendocument/droid/background/RawLoader.kt +++ /dev/null @@ -1,68 +0,0 @@ -package app.opendocument.droid.background - -import android.content.Context -import android.net.Uri -import java.io.File - -/** - * The two files [CoreLoader] does not get: svg and xml, which the WebView draws by itself. - * - * [SupportedDocumentTypes.isRenderedByRaw] decides, and [LoaderService] asks it before the core. - */ -class RawLoader(context: Context?) : FileLoader(context, LoaderType.RAW) { - - override fun isSupported(options: Options): Boolean = - SupportedDocumentTypes.isRenderedByRaw(options.fileType, nameExtension(options)) - - override fun loadSync(options: Options) { - val result = Result(type, options) - - try { - val fileType = checkNotNull(options.fileType) { "no file type detected" } - val fileExtension = nameExtension(options) - - val cacheFile = - checkNotNull(AndroidFileCache.getCacheFile(context, checkNotNull(options.cacheUri))) - val cacheDirectory = AndroidFileCache.getCacheDirectory(cacheFile) - - val finalUri: Uri - if (SupportedDocumentTypes.isSvg(fileType, fileExtension)) { - // the browser does not recognize an svg not called ".svg", and the cached copy - // has no name of its own - val extension = "svg" - - val htmlFile = File(cacheDirectory, "image.html") - StreamUtil.copy(context.assets.open("image.html"), htmlFile) - - val imageFile = File(cacheDirectory, "image.$extension") - StreamUtil.copy(cacheFile, imageFile) - - finalUri = - Uri.fromFile(htmlFile) - .buildUpon() - .appendQueryParameter("ext", extension) - .build() - } else { - // xml, the only case left: the WebView goes by the name, and a shared file - // typed application/xml need not have an extension of its own - val renamedFile = File(cacheDirectory, "temp.${fileExtension ?: "xml"}") - StreamUtil.copy(cacheFile, renamedFile) - - finalUri = Uri.fromFile(renamedFile) - } - - result.partTitles.add(null) - result.partUris.add(finalUri) - callOnSuccess(result) - } catch (e: Throwable) { - callOnError(result, e) - } - } - - /** - * Not [Options.fileExtension]: [MimeTypeResolver.resolve] lets the detected mime type's - * canonical extension win, so an xml the core called `text/plain` arrives there as "txt". - */ - private fun nameExtension(options: Options): String? = - MimeTypeResolver.parseExtension(options.filename) -} diff --git a/app/src/main/java/app/opendocument/droid/background/SupportedDocumentTypes.kt b/app/src/main/java/app/opendocument/droid/background/SupportedDocumentTypes.kt index 2386841aa736..d03c4554ac75 100644 --- a/app/src/main/java/app/opendocument/droid/background/SupportedDocumentTypes.kt +++ b/app/src/main/java/app/opendocument/droid/background/SupportedDocumentTypes.kt @@ -28,12 +28,6 @@ object SupportedDocumentTypes { Odr.allFileTypes().filter { Odr.capabilitiesByFileType(it).translateHtml } } - /** - * The core would render this into an ``, but [RawLoader] is asked first and hands the - * WebView the file itself, which draws it just as well. - */ - private const val SVG_MIME_TYPE = "image/svg+xml" - /** * What the app offers itself for: the core's document formats plus the three non-document ones * worth opening a viewer for. Much narrower than [CORE_FILE_TYPES] - see the class doc. @@ -54,9 +48,6 @@ object SupportedDocumentTypes { */ private val CLAIMED_MIME_PREFIXES = listOf("image/") - /** Unknown to the core. Never claimed - only a catch-all or a share gets one here. */ - private val XML_MIME_TYPES = setOf("application/xml", "text/xml") - /** Every mime type spelling odrcore accepts for a format [CoreLoader] renders. */ private val CORE_MIME_TYPES: Set by lazy { mimeTypesOf(CORE_FILE_TYPES) } @@ -77,7 +68,7 @@ object SupportedDocumentTypes { /** * odrcore's canonical spelling of [mimeType], so one spelling per format flows downstream - the * app claims every spelling in the core's table, `application/csv` and `multipart/x-zip` - * included. Anything the core does not name - svg, xml - passes through untouched. + * included. Anything the core does not name passes through untouched. */ fun canonicalMimeType(mimeType: String?): String? { if (mimeType == null) { @@ -96,38 +87,6 @@ object SupportedDocumentTypes { fun isRenderedByCore(mimeType: String?): Boolean = mimeType != null && mimeType.lowercase() in CORE_MIME_TYPES - /** - * Whether [RawLoader] takes this instead of [CoreLoader], which is asked after it. - * - * [extension] because an xml *is* text to the detection: `Odr.mimetype` says `text/plain` and - * the provider's `application/xml` never arrives. See [nameSays]. - */ - fun isRenderedByRaw(mimeType: String?, extension: String? = null): Boolean = - isSvg(mimeType, extension) || isXml(mimeType, extension) - - /** Svg, which the WebView draws by itself - see [SVG_MIME_TYPE]. */ - fun isSvg(mimeType: String?, extension: String? = null): Boolean = - mimeType?.lowercase() == SVG_MIME_TYPE || nameSays(mimeType, extension, "svg") - - /** Xml, which odrcore names but has no decoder for, and which the WebView shows as it is. */ - fun isXml(mimeType: String?, extension: String? = null): Boolean = - mimeType?.lowercase() in XML_MIME_TYPES || nameSays(mimeType, extension, "xml") - - /** - * Whether the file is called `.expected` *and* the detection left room for the name to know - * better - plain text, or nothing recognized. The bytes win otherwise: a `drawing.svg` holding - * an odt is an odt. - */ - private fun nameSays(mimeType: String?, extension: String?, expected: String): Boolean { - if (extension?.lowercase() != expected) { - return false - } - - val fileType = mimeType?.let { Odr.fileTypeByMimetype(it) } ?: return true - - return fileType == FileType.UNKNOWN || fileType == FileType.TEXT_FILE - } - /** * Whether the core files this as a document rather than text, an image, an archive, a font or * media. diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt index f55293b55439..2fa87072a5eb 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt @@ -269,11 +269,11 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { } private fun loadWithType(loaderType: FileLoader.LoaderType, options: FileLoader.Options) { - // whatever the last load had to say was about the last document. the offers to reopen or - // upload are indefinite, so without this they sit over the document that came after them + // whatever the last load had to say was about the last document. the offer to reopen is + // indefinite, so without this it sits over the document that came after it SnackbarHelper.dismiss(requireActivity()) - showProgress(loaderType == FileLoader.LoaderType.ONLINE) + showProgress() state.beginLoadIdling() @@ -399,22 +399,15 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { R.drawable.ic_edit, ) - // read aloud needs the javascript bridge, which PageView keeps off the third party viewers - // an ONLINE result loads - offering it there leaves it stuck at "reading" with no paragraph - val tts = - if (result.loaderType == FileLoader.LoaderType.ONLINE) null - else - DocumentActions.Action( - DocumentActions.ACTION_TTS, - R.string.menu_tts, - R.drawable.ic_volume_up, - ) - // the order they unfold in, most wanted first val unfolding = listOfNotNull( edit, - tts, + DocumentActions.Action( + DocumentActions.ACTION_TTS, + R.string.menu_tts, + R.drawable.ic_volume_up, + ), DocumentActions.Action( DocumentActions.ACTION_SHARE, R.string.menu_share, @@ -534,10 +527,7 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { prepareActions(result) // the escape hatch for a file we show rather than read: an image, an archive listing - if ( - !SupportedDocumentTypes.isDocument(options.fileType) || - result.loaderType == FileLoader.LoaderType.ONLINE - ) { + if (!SupportedDocumentTypes.isDocument(options.fileType)) { offerReopen(activity, options, R.string.toast_hint_unsupported_file, false) } @@ -574,10 +564,7 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { offerReopen(activity, options, R.string.toast_error_find_file, true) error is OutOfMemoryError -> offerReopen(activity, options, R.string.toast_error_out_of_memory, true) - // the network or the server failed, which says nothing about the file - result.loaderType == FileLoader.LoaderType.ONLINE -> - offerReopen(activity, options, R.string.toast_error_upload_failed, true) - // unreadable, or named and still not openable - neither is worth an upload + // unreadable, or named and still not openable else -> { // nothing is ever going to be shown for this file, so drop back to the // landing screen and let the dialog come up over that @@ -643,19 +630,8 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { unload() dismissProgress() - if (result.loaderType == FileLoader.LoaderType.CORE) { - if (serviceQueue.service?.isOnlineSupported(options) == true) { - // the upload offer is the app still having something to try, so the document stays - // where it is until the user answers it - offerUpload(activity, options) - } else { - offerReopen(activity, options, R.string.toast_error_illegal_file_reopen, true) - giveUp(activity) - } - } else if (result.loaderType == FileLoader.LoaderType.ONLINE) { - offerReopen(activity, options, R.string.toast_error_illegal_file_reopen, true) - giveUp(activity) - } + offerReopen(activity, options, R.string.toast_error_illegal_file_reopen, true) + giveUp(activity) state.endLoadIdling() } @@ -690,51 +666,13 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { * Nothing left to try with this document, so stop showing it: the landing screen is a better * answer than a blank page, and the bar raised just before this says what happened. * - * Not every failure ends here. The password prompt and the upload offer are both the app still - * having something to do with the file, and both need the document on screen to do it. + * Not every failure ends here. The password prompt is the app still having something to do with + * the file, and it needs the document on screen to do it. */ private fun giveUp(activity: Activity) { (activity as MainActivity).closeFailedDocument() } - private fun offerUpload(activity: Activity, options: FileLoader.Options) { - val fileType = options.fileType - - analyticsManager.report( - "upload_offer_invasive", - AnalyticsConstants.PARAM_CONTENT_TYPE, - fileType, - AnalyticsConstants.PARAM_CONTENT, - options.originalUri, - ) - - val builder = AlertDialog.Builder(activity) - builder.setTitle(R.string.toast_error_illegal_file) - builder.setMessage(R.string.dialog_upload_file) - - builder.setPositiveButton(getString(R.string.action_upload)) { dialog, _ -> - analyticsManager.report("load_upload", AnalyticsConstants.PARAM_CONTENT_TYPE, fileType) - - loadWithType(FileLoader.LoaderType.ONLINE, options) - - dialog.dismiss() - } - builder.setNegativeButton(getString(android.R.string.cancel)) { dialog, _ -> - analyticsManager.report( - "load_upload_cancel", - AnalyticsConstants.PARAM_CONTENT_TYPE, - fileType, - ) - - offerReopen(activity, options, R.string.toast_error_illegal_file_reopen, true) - giveUp(activity) - - dialog.dismiss() - } - - builder.show() - } - private fun offerContact(activity: Activity) { analyticsManager.report("contact_offer") @@ -901,7 +839,7 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { } } - private fun showProgress(isUpload: Boolean) { + private fun showProgress() { // getParentFragmentManager() throws when the fragment is not attached, where the // deprecated getFragmentManager() used to return null if (!isAdded) { @@ -921,7 +859,7 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener { } try { - val progressDialog = ProgressDialogFragment(isUpload) + val progressDialog = ProgressDialogFragment() this.progressDialog = progressDialog progressDialog.show(fragmentManager, ProgressDialogFragment.FRAGMENT_TAG) diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt index 24ecbefdffb5..d6e213eb85cd 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt @@ -811,7 +811,7 @@ class MainActivity : AppCompatActivity() { * altogether. The price is that other unnamed binaries stay listed too, which is the harmless * half of the trade. * - * Images are not here, though [RawLoader] shows them: this is a document reader's file picker, + * Images are not here, though [CoreLoader] shows them: this is a document reader's file picker, * and an image reaches it by being shared or opened from a gallery. */ private fun pickableMimeTypes(): Array = diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt b/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt index 064f1e09b5dd..db06bd9fbd06 100644 --- a/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt +++ b/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt @@ -19,7 +19,6 @@ import androidx.annotation.Keep import androidx.webkit.WebSettingsCompat import androidx.webkit.WebViewFeature import app.opendocument.droid.background.AndroidFileCache -import app.opendocument.droid.background.OnlineLoader import app.opendocument.droid.background.StreamUtil import app.opendocument.droid.nonfree.CrashManager import app.opendocument.droid.ui.ParagraphListener @@ -111,14 +110,8 @@ constructor(context: Context, attributeSet: AttributeSet?) : @Suppress("DEPRECATION") // the request based overload needs API 24 semantics override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - if ( - url.startsWith(OnlineLoader.GOOGLE_VIEWER_URL) || - url.startsWith(OnlineLoader.MICROSOFT_VIEWER_URL) || - url.contains("officeapps.live.com/") - ) { - return false - } - + // everything this view shows is served from localhost, so a link out of it is + // a link out of the app return try { getContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/ProgressDialogFragment.kt b/app/src/main/java/app/opendocument/droid/ui/widget/ProgressDialogFragment.kt index 1543300de177..95b594a01bf9 100644 --- a/app/src/main/java/app/opendocument/droid/ui/widget/ProgressDialogFragment.kt +++ b/app/src/main/java/app/opendocument/droid/ui/widget/ProgressDialogFragment.kt @@ -1,6 +1,5 @@ package app.opendocument.droid.ui.widget -import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle import android.widget.TextView @@ -11,29 +10,19 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder /** * Built on MaterialAlertDialogBuilder rather than the framework ProgressDialog, which resolves * android:alertDialogTheme and so would have stayed light once the app theme became DayNight. - * - * @JvmOverloads keeps the no-arg constructor the fragment framework re-creates this with. */ -@SuppressLint("ValidFragment") -class ProgressDialogFragment @JvmOverloads constructor(private val isUpload: Boolean = false) : - DialogFragment() { +class ProgressDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val title = if (isUpload) R.string.dialog_uploading_title else R.string.dialog_loading_title - - var message = getString(R.string.dialog_generic_loading_message) - if (isUpload) { - message += " " + getString(R.string.dialog_uploading_message_appendix) - } - val view = layoutInflater.inflate(R.layout.dialog_progress, null) - view.findViewById(R.id.progress_message).text = message + view.findViewById(R.id.progress_message).text = + getString(R.string.dialog_generic_loading_message) // known issue that causes infinite progressdialog setCancelable(true) return MaterialAlertDialogBuilder(requireContext()) - .setTitle(title) + .setTitle(R.string.dialog_loading_title) .setView(view) .setCancelable(true) .create() diff --git a/app/src/main/res/values-ca-rES/strings.xml b/app/src/main/res/values-ca-rES/strings.xml index ae83a9a811f8..39e222df46e0 100644 --- a/app/src/main/res/values-ca-rES/strings.xml +++ b/app/src/main/res/values-ca-rES/strings.xml @@ -2,12 +2,9 @@ Hi ha hagut un problema. No s\'ha pogut obrir el fitxer. No s\'ha pogut trobar el fitxer. És possible que ja no existeixi? - No sembla que sigui un format de fitxer compatible. S\'ha esgotat la memòria del telèfon. Massa imatges o fitxer massa gran. El document està protegit per contrasenya S\'està carregant… - S\'està pujant… - No s\'ha pogut obrir el document perquè el seu format no és compatible. Voleu pujar-lo temporalment al nostre servidor per tal que us el puguem mostrar de totes maneres? Cerca Google Cloud Print Text-a-paraula diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 7a43fe8ee430..7d07fed72be1 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -2,12 +2,9 @@ Hi ha hagut un problema. No s\'ha pogut obrir el fitxer. No s\'ha pogut trobar el fitxer. És possible que ja no existeixi? - No sembla que sigui un format de fitxer compatible. S\'ha esgotat la memòria del telèfon. Massa imatges o fitxer massa gran. El document està protegit per contrasenya S\'està carregant… - S\'està pujant… - No s\'ha pogut obrir el document perquè el seu format no és compatible. Voleu pujar-lo temporalment al nostre servidor per tal que us el puguem mostrar de totes maneres? Cerca Mode de pantalla completa Google Cloud Print diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index b2e8872661d6..86bb362a095b 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -2,12 +2,9 @@ Něco se pokazilo. Nelze otevřít soubor. Soubor nebyl nalezen. Možná už neexistuje. - Pravděpodobně se nejedná o podporovaný formát souboru. Nedostatek paměti v telefonu! Soubor je příliš velký, nebo obsahuje příliš mnoho obrázků. Dokument je chráněn heslem Probíhá načítání… - Probíhá odesílání… - Nelze otevřít soubor, protože nepodporujeme jeho formát. Chcete jej zobrazit tak, že jej dočasně nahrajete na náš server? Vyhledat Upravit dokument Google Cloud Print diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 98f98d1eeb9c..8222a4dcc9f2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -2,12 +2,9 @@ Něco se pokazilo. Nelze otevřít soubor. Soubor nebyl nalezen. Možná už neexistuje. - Pravděpodobně se nejedná o podporovaný formát souboru. Nedostatek paměti v telefonu! Soubor je příliš velký, nebo obsahuje příliš mnoho obrázků. Dokument je chráněn heslem Probíhá načítání… - Probíhá odesílání… - Nelze otevřít soubor, protože nepodporujeme jeho formát. Chcete jej zobrazit tak, že jej dočasně nahrajete na náš server? Vyhledat Upravit dokument Celá obrazovka diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 550e6e876f81..301d039f02c7 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -2,12 +2,9 @@ Der er gået noget galt. Filen kunne ikke åbnes. Kunne ikke finde filen. Måske eksisterer den ikke længere? - Det ser ikke ud til, at denne filtype kan bruges. Enheden har ikke mere plads i hukommelsen. Filen er enten for stor eller indeholder for mange billeder. Dokumentet er låst med et kodeord Indlæser … - Uploader … - Dokumentet kan desværre ikke åbnes, fordi appen ikke understøtter formatet. Vil du uploade din fil til vores server midlertidigt, så vi kan åbne den alligevel? Søg Rediger dokument Fjern reklamer diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index af1932acb052..ce8cbadd5360 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -2,12 +2,9 @@ Der er gået noget galt. Filen kunne ikke åbnes. Kunne ikke finde filen. Måske eksisterer den ikke længere? - Det ser ikke ud til, at denne filtype kan bruges. Enheden har ikke mere plads i hukommelsen. Filen er enten for stor eller indeholder for mange billeder. Dokumentet er låst med et kodeord Indlæser … - Uploader … - Dokumentet kan desværre ikke åbnes, fordi appen ikke understøtter formatet. Vil du uploade din fil til vores server midlertidigt, så vi kan åbne den alligevel? Søg Rediger dokument Fjern reklamer diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 506949e37be3..607493968259 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -2,16 +2,12 @@ Etwas schlimmes ist passiert. Datei konnte nicht geöffnet werden. Datei konnte nicht gefunden werden. - Diese Datei scheint keines der unterstützten Formate zu haben. Kein Ort zum Speichern der Datei ausgewählt. Datei konnte nicht gespeichert werden. Bitte kontaktiere support@opendocument.app Speicher geht zur Neige! Zu viele Bilder oder eine zu große Datei. Dokument ist passwort-geschützt Lädt… - Lädt hoch… Bitte warten. Dies kann einige Minuten dauern. - Hochgeladene Dateien sind anonym und werden nach 24 Stunden automatisch gelöscht. - Dieses Dokument kann nicht geöffnet werden, weil wir dessen Format nicht unterstützen. Willst du es temporär auf unsere Server hochladen, damit wir es trotzdem für dich anzeigen können? Hochgeladene Dateien sind privat und werden automatisch nach 24 Stunden gelöscht. In Dokument suchen Dokument bearbeiten Werbung entfernen diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 618c40f7ebcf..12918cdbb17a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2,16 +2,12 @@ Etwas Schlimmes ist passiert. Datei konnte nicht geöffnet werden. Datei konnte nicht gefunden werden. - Diese Datei scheint keines der unterstützten Formate zu haben. Kein Ort zum Speichern der Datei ausgewählt. Datei konnte nicht gespeichert werden. Bitte kontaktiere support@opendocument.app Speicher geht zur Neige! Zu viele Bilder oder eine zu große Datei. Dokument ist passwort-geschützt Lädt… - Lädt hoch… Bitte warten. Dies kann einige Minuten dauern. - Hochgeladene Dateien sind anonym und werden nach 24 Stunden automatisch gelöscht. - Dieses Dokument kann nicht geöffnet werden, weil wir dessen Format nicht unterstützen. Willst du es temporär auf unsere Server hochladen, damit wir es trotzdem für dich anzeigen können? Hochgeladene Dateien sind privat und werden automatisch nach 24 Stunden gelöscht. In Dokument suchen Dokument bearbeiten Werbung entfernen diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 898af925d1a0..089b1e77db1b 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -2,15 +2,11 @@ Ha pasado algo malo. No se ha podido abrir el archivo. Ha sido imposible encontrar el archivo. ¿Seguro que no se ha eliminado? - Parece que este formato de archivo no es compatible. ¿No está contento con cómo se muestra el archivo? Abrir en su lugar en otra aplicación. ¡El teléfono se ha quedado sin memoria! Hay demasiadas imágenes o el archivo es demasiado grande. El documento está protegido con contraseña. Cargando… - Subiendo… Por favor, espere, esto podría tardar unos minutos. - Los archivos cargados son privados y automáticamente eliminados después de 24 horas. - No podemos abrir este documento porque el formato no es compatible. ¿Desea subirlo temporalmente a nuestro servidor para que podamos mostrárselo? Los archivos subidos son privados y se eliminan automáticamente después de 24 horas. Buscar Editar Eliminar anuncios diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b002509f1027..87e7a9775919 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2,15 +2,11 @@ Ha pasado algo malo. No se ha podido abrir el archivo. Ha sido imposible encontrar el archivo. ¿Seguro que no se ha eliminado? - Parece que este formato de archivo no es compatible. ¿No está contento con cómo se muestra el archivo? Abrir en su lugar en otra aplicación. ¡El teléfono se ha quedado sin memoria! Hay demasiadas imágenes o el archivo es demasiado grande. El documento está protegido con contraseña. Cargando… - Subiendo… Por favor, espere, esto podría tardar unos minutos. - Los archivos cargados son privados y automáticamente eliminados después de 24 horas. - No podemos abrir este documento porque el formato no es compatible. ¿Desea subirlo temporalmente a nuestro servidor para que podamos mostrárselo? Los archivos subidos son privados y se eliminan automáticamente después de 24 horas. Buscar Editar Eliminar anuncios diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 4ef1743aa2a9..590e0861040b 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -3,7 +3,6 @@ OpenDocument Reader Une erreur est survenue. Impossible d\'ouvrir le fichier. Impossible de trouver le fichier. Il n\'existe peut-être plus ? - Ce format de fichier ne semble pas être supporté. Le format de ce fichier n\'est pas pris en charge. Essayez de l\'ouvrir avec une autre application. Vous n\'êtes pas satisfaits de l\'affichage du fichier ? Ouvrez-le avec une autre application. Aucun dossier pour enregistrer le fichier sélectionné. @@ -11,10 +10,7 @@ Place insuffisante dans la mémoire de l\'appareil ! Il y a trop d\'images ou les fichiers sont trop volumineux. Ce document est protégé par mot de passe Chargement en cours… - Téléchargement en cours… Veuillez patienter. Cette action peut prendre quelques minutes. - Les fichiers envoyés sont privés et supprimés automatiquement au bout de 24 heures. - Impossible d\'ouvrir ce document car son format n\'est pas supporté. Voulez-vous l\'héberger temporairement sur notre serveur pour le visualiser ? Les fichiers téléchargés sont privés et automatiquement supprimés après 24 heures. Rechercher Ouvrir avec… Partager le document diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 000950c44995..d331802e3627 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -3,7 +3,6 @@ OpenDocument Reader Une erreur est survenue. Impossible d\'ouvrir le fichier. Impossible de trouver le fichier. Il n\'existe peut-être plus ? - Ce format de fichier ne semble pas être supporté. Le format de ce fichier n\'est pas pris en charge. Essayez de l\'ouvrir avec une autre application. Vous n\'êtes pas satisfaits de l\'affichage du fichier ? Ouvrez-le avec une autre application. Aucun dossier pour enregistrer le fichier sélectionné. @@ -11,10 +10,7 @@ Place insuffisante dans la mémoire de l\'appareil ! Il y a trop d\'images ou les fichiers sont trop volumineux. Ce document est protégé par mot de passe Chargement en cours… - Téléchargement en cours… Veuillez patienter. Cette action peut prendre quelques minutes. - Les fichiers envoyés sont privés et supprimés automatiquement au bout de 24 heures. - Impossible d\'ouvrir ce document car son format n\'est pas supporté. Voulez-vous l\'héberger temporairement sur notre serveur pour le visualiser ? Les fichiers téléchargés sont privés et automatiquement supprimés après 24 heures. Rechercher Modifier Supprimer les publicités diff --git a/app/src/main/res/values-ga-rIE/strings.xml b/app/src/main/res/values-ga-rIE/strings.xml index bdeecc7bf22f..a5562267c914 100644 --- a/app/src/main/res/values-ga-rIE/strings.xml +++ b/app/src/main/res/values-ga-rIE/strings.xml @@ -2,7 +2,6 @@ Tharla taisme. Níorbh fhéidir an comhad a oscailt. Níorbh fhéidir an comhad a fháil. B\'fhéidir nach ann dó a thuilleadh? - Tá an dealramh air nach dtugtar tacaíocht den chineál comhad sin. Formáid chomhaid nach dtugtar tacaíocht di. Féach í a oscailt i bhfeidhmchláirín eile. An é nach bhfuil tú sásta leis an mbealach ina thaispeántar an comhad? Oscail le feidhmchlár eile é ina ionad sin. Níl aon áit chun an comhad a chur i dtaisce. @@ -10,10 +9,7 @@ Níl cuimhne fágtha sa ghuthán! Tá an iomarca grianghraif ann nó tá an comhad rómhór. Tá an cháipéis faoi chosaint focal faire Ag luchtú… - Á uasluchtú… Fan. B\'fhéidir go dtógfaidh sé seo roinnt nóiméid. - Bíonn comhaid uasluchtaithe príobháideach agus scriostar iad tar éis 24 uair an chloig. - Ní thig linn an cháipéis seo a oscailt mar ní thugtar tacaíocht don chineál comhad sin. An mian leat í a uasluchtú go sealadach chuig ár bhfreastalaí ionas gur féidir linn í a thaispeáint duit? Cuardaigh Oscail le... Comhroinn an cháipéis diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index 34e28b4fd614..09780c23b697 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -2,15 +2,11 @@ Tharla taisme. Níorbh fhéidir an comhad a oscailt. Níorbh fhéidir an comhad a fháil. B\'fhéidir nach ann dó a thuilleadh? - Tá an dealramh air nach dtugtar tacaíocht den chineál comhad sin. An é nach bhfuil tú sásta leis an mbealach ina thaispeántar an comhad? Oscail le feidhmchlár eile é ina ionad sin. Níl cuimhne fágtha sa ghuthán! Tá an iomarca grianghraif ann nó tá an comhad rómhór. Tá an cháipéis faoi chosaint focal faire Ag luchtú… - Á uasluchtú… Fan. B\'fhéidir go dtógfaidh sé seo roinnt nóiméid. - Bíonn comhaid uasluchtaithe príobháideach agus scriostar iad tar éis 24 uair an chloig. - Ní thig linn an cháipéis seo a oscailt mar ní thugtar tacaíocht don chineál comhad sin. An mian leat í a uasluchtú go sealadach chuig ár bhfreastalaí ionas gur féidir linn í a thaispeáint duit? Cuardaigh Cuir an cháipéis in eagar Bain na fógraí diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 5dcc546ebee8..a54a4d9d8d31 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -2,7 +2,6 @@ कुछ गलत हो गया है।फ़ाइल नहीं खोल सका। फ़ाइल नहीं मिली। शायद यह अब मौजूद नहीं है? - यह एक समर्थित file format नहीं है। असमर्थित फ़ाइल format। इसे किसी अन्य app में खोलने का प्रयास करें। आप इसे प्रदर्शित किए जाने के तरीके से खुश नहीं हैं। इसे किसी दूसरे एप्प में खोले। फ़ाइल को save करने के लिए कोई स्थान नहीं चुना गया है। @@ -10,7 +9,6 @@ फ़ोन मे मेमोरी नही है। फ़ोन में बहुत अधिक चित्र हैं, या फ़ाइल बहुत बड़ी है। यह फ़ाइल पासवर्ड द्वारा सुरक्षित है। लोड हो रहा है… - अपलोड हो रहा है... कृपया प्रतीक्षा करें, इसमें कुछ सेकंड लग सकते हैं के साथ ओपन करें... दस्तावेज़ साझा करें diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index adcf24aeb59f..22b71223e88f 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -2,7 +2,6 @@ Si è verificato un errore. Impossibile aprire il file. Impossibile trovare il file. Forse non esiste più? - Questo non sembra essere un formato di file supportato. Formato file non supportato. Prova ad aprirlo in un\'altra app. Non sei soddisfatto di come viene visualizzato il documento? Aprilo in un\'altra app. Nessun posto in cui salvare il file da scegliere. @@ -10,10 +9,7 @@ Memoria del telefono esaurita! Troppe immagini o file troppo grandi. Il documento è protetto da password. Caricamento in corso… - Caricamento... Si prega di attendere. Questo potrebbe richiedere alcuni minuti. - I file caricati sono privati e automaticamente cancellati dopo 24 ore. - Non siamo in grado di aprire questo documento, perché non supportiamo il suo formato. Vuoi caricarlo temporaneamente sul nostro server, in modo da poter visualizzarlo comunque? I file caricati sono privati e cancellati automaticamente dopo 24 ore. Cerca nel documento Apri con... Condividi documento diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 82877ae2c276..367abc43d5af 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2,12 +2,9 @@ Si è verificato un errore. Impossibile aprire il file. Impossibile trovare il file. Forse non esiste più? - Questo non sembra essere un formato di file supportato. Memoria del telefono esaurita! Troppe immagini o file troppo grandi. Il documento è protetto da password. Caricamento in corso… - Caricamento... - Non siamo in grado di aprire questo documento, perché non supportiamo il suo formato. Vuoi caricarlo temporaneamente sul nostro server, in modo da poter visualizzarlo comunque? I file caricati sono privati e cancellati automaticamente dopo 24 ore. Cerca nel documento Modifica documento Rimuovi pubblicità diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 364a49b64d00..76e7876d7043 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -2,7 +2,6 @@ 何か問題が発生しました。ファイルを開くことができませんでした。 ファイルが見つかりません。もう存在しないかもしれません? - これはサポートされているファイル形式ではないようです。 サポートしないファイル形式です。別のアプリで開いてみてください。 ファイルの表示方法に満足できませんか? 代わりに別のアプリで開きます。 選択したファイルを保存する場所がありません。 @@ -10,10 +9,7 @@ デバイスのメモリ不足です! 写真が多すぎるかファイルが大きすぎます。 ドキュメントがパスワードで保護されています 読み込んでいます… - アップロードしています… しばらくお待ちください。数分かかることがあります。 - アップロードされたファイルは保護され、24 時間後に自動的に削除されます。 - その形式をサポートしていないため、このドキュメントを開くことができません。一時的に私たちのサーバーにアップロードして、とにかくそれを表示できるようにしますか? アップロードされたファイルはプライベートで、24 時間後に自動的に削除されます。 ドキュメントの検索 開く... ドキュメントを共有 diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8c3221ae4635..e2afa76fba86 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2,16 +2,12 @@ 何か問題が発生しました。ファイルを開くことができませんでした。 ファイルが見つかりません。もう存在しないかもしれません? - これはサポートされているファイル形式ではないようです。 サポートしないファイル形式です。別のアプリで開いてみてください。 ファイルの表示方法に満足できませんか? 代わりに別のアプリで開きます。 デバイスのメモリ不足です! 写真が多すぎるかファイルが大きすぎます。 ドキュメントがパスワードで保護されています 読み込んでいます… - アップロードしています… しばらくお待ちください。数分かかることがあります。 - アップロードされたファイルは保護され、24 時間後に自動的に削除されます。 - その形式をサポートしていないため、このドキュメントを開くことができません。一時的に私たちのサーバーにアップロードして、とにかくそれを表示できるようにしますか? アップロードされたファイルはプライベートで、24 時間後に自動的に削除されます。 ドキュメントの検索 ドキュメントを編集 広告を削除する diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 92f76bd49166..07a26d999310 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -2,7 +2,6 @@ 문제가 발생해서 파일을 열지 못 했습니다 파일이 없어서 더 이상 찾을 수 없습니다. - 지원하지 않는 파일입니다. 지원하지 않는 파일입니다. 다른 앱에서 열어 보세요. 열린 파일이 제대로 보여지지 않습니까? 다른 앱에서 여시겠습니까? 선택한 파일을 저장할 위치가 없습니다. @@ -10,10 +9,7 @@ 메모리가 부족합니다! 사진이 너무 많거나 파일이 너무 큽니다. 문서가 암호로 잠겨 있습니다. 로딩 중... - 업로드 중... 잠시 기다려 주세요. - 업로드한 파일은 비공개이고, 24시간 뒤에 자동 삭제됩니다. - 이 포맷을 지원하지 않기에 문서를 열 수 없습니다. 임시로 서버에 업로드해서 여시겠습니까? 업로드한 파일은 비공개이고, 24 시간 뒤에 자동으로 삭제합니다. 문서 검색 다른 앱으로 열기... 문서 공유 diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 57ac0c7e412f..e8726bd059b5 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -2,12 +2,9 @@ Jest jakaś przeszkoda. Nie można otworzyć pliku. Nie można znaleźć pliku. Być może już nie istnieje? - Ten format pliku nie jest obsługiwany. Brak pamięci telefonu! Zbyt wiele zdjęć lub plik jest za duży. Dokument zabezpieczony jest hasłem Pobieranie… - Wysyłanie... - Nie możemy otworzyć tego dokumentu, ponieważ nie obsługujemy jego formatu. Czy chcesz tymczasowo przesłać go na nasz serwer, abyśmy mimo wszystko mogli go wyświetlić? Przesłane pliki są prywatne i automatycznie usuwane są po 24 godzinach. Szukaj w dokumencie Edytuj dokument Usuń reklamy diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 91047e044659..90d27f0ec431 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2,12 +2,9 @@ Jest jakaś przeszkoda. Nie można otworzyć pliku. Nie można znaleźć pliku. Być może już nie istnieje? - Ten format pliku nie jest obsługiwany. Brak pamięci telefonu! Zbyt wiele zdjęć lub plik jest za duży. Dokument zabezpieczony jest hasłem Pobieranie… - Wysyłanie... - Nie możemy otworzyć tego dokumentu, ponieważ nie obsługujemy jego formatu. Czy chcesz tymczasowo przesłać go na nasz serwer, abyśmy mimo wszystko mogli go wyświetlić? Przesłane pliki są prywatne i automatycznie usuwane są po 24 godzinach. Szukaj w dokumencie Edytuj dokument Usuń reklamy diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4bebfdd0b596..c0cfbafa5c20 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2,7 +2,6 @@ Houve um problema. Não foi possível abrir o arquivo. Não foi possível encontrar o arquivo. Talvez ele não exista mais. - Parece que este formato de arquivo não é suportado. Formato de arquivo não suportado. Tente abri-lo em outro aplicativo. Não está satisfeito com a forma como o arquivo é exibido? Abra em outro aplicativo. Não há lugar para salvar o arquivo a escolher. @@ -10,10 +9,7 @@ Telefone sem memória! Muitas fotos ou arquivo muito grande. O documento está protegido por senha Carregando… - Fazendo upload… Por favor, aguarde, isso pode levar alguns minutos. - Os arquivos enviados são privados e automaticamente excluídos após 24 horas. - Não conseguimos abrir este documento, pois não suportamos este formato. Você deseja fazer o upload dele em nosso servidor temporariamente, para que possamos exibi-lo para você? Pesquisa Abrir com... Compartilhar documento diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index c3350346615f..7fe88cec4b91 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -2,12 +2,9 @@ Произошла ошибка. Не удалось открыть файл. Не удалось найти файл. Возможно, его больше не существует? - Возможно, этот формат файла не поддерживается. Недостаточно памяти на телефоне! Слишком много изображений, или файл слишком большой. Документ защищён паролем Загрузка… - Выгрузка... - Мы не можем открыть этот документ, потому что его формат не поддерживается. Хотите загрузить его на наш сервер временно, чтобы его можно было отобразить? Загруженные файлы предназначены только для вашего личного использования и автоматически удаляются через 24 часа. Поиск в документе Редактировать документ Убрать рекламные объявления diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9372e2a9bbda..3741c04ea7de 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2,12 +2,9 @@ Произошла ошибка. Не удалось открыть файл. Не удалось найти файл. Возможно, его больше не существует? - Возможно, этот формат файла не поддерживается. Недостаточно памяти на телефоне! Слишком много изображений, или файл слишком большой. Документ защищён паролем Загрузка… - Выгрузка... - Мы не можем открыть этот документ, потому что его формат не поддерживается. Хотите загрузить его на наш сервер временно, чтобы его можно было отобразить? Загруженные файлы предназначены только для вашего личного использования и автоматически удаляются через 24 часа. Поиск в документе Редактировать документ Убрать рекламные объявления diff --git a/app/src/main/res/values-sl-rSI/strings.xml b/app/src/main/res/values-sl-rSI/strings.xml index 306dba3cf48c..7a9479c0e8c4 100644 --- a/app/src/main/res/values-sl-rSI/strings.xml +++ b/app/src/main/res/values-sl-rSI/strings.xml @@ -2,7 +2,6 @@ Nekaj je šlo narobe. Datoteke ni bilo mogoče odpreti. Datoteke ni bilo mogoče najti. Morda ne obstaja več? - Videti je, da ta oblika datoteke ni podprta. Nepodprta oblika datoteke. Poizkusite jo odpreti v drugem programu. Niste zadovoljni z načinom prikaza datoteke? Odprite jo v drugem programu. Mesto za shranjevanje datoteke ni izbrano. @@ -10,10 +9,7 @@ Telefonu je zmanjkalo pomnilnika! Preveč slik ali prevelika datoteka. Dokument je zaščiten z geslom Nalaganje … - Pošiljanje … Počakajte, to lahko traja nekaj minut. - Poslane datoteke so zasebne in se samodejno izbrišejo po 24-ih urah. - Tega dokumenta ne moremo odpreti, ker ne podpiramo njegove oblike. Ali ga želite začasno poslati na naš strežnik, da ga lahko vseeno prikažemo? Išči Odpri s/z … Deli dokument diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index a1e4878da369..344887615258 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -2,7 +2,6 @@ Nekaj je šlo narobe. Datoteke ni bilo mogoče odpreti. Datoteke ni bilo mogoče najti. Morda ne obstaja več? - Videti je, da ta oblika datoteke ni podprta. Nepodprta oblika datoteke. Poizkusite jo odpreti v drugem programu. Niste zadovoljni z načinom prikaza datoteke? Odprite jo v drugem programu. Mesto za shranjevanje datoteke ni izbrano. @@ -10,10 +9,7 @@ Telefonu je zmanjkalo pomnilnika! Preveč slik ali prevelika datoteka. Dokument je zaščiten z geslom Nalaganje … - Pošiljanje … Počakajte, to lahko traja nekaj minut. - Poslane datoteke so zasebne in se samodejno izbrišejo po 24-ih urah. - Tega dokumenta ne moremo odpreti, ker ne podpiramo njegove oblike. Ali ga želite začasno poslati na naš strežnik, da ga lahko vseeno prikažemo? Išči Uredi Odstrani oglase diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 12a5d8b2deba..b05f7bcb9b43 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -2,7 +2,6 @@ Kötü bir şey oldu. Dosya açılamadı. Dosya bulunamadı. Belki artık mevcut değil? - Bu, desteklenen bir dosya biçimi gibi görünmüyor. Desteklenmeyen dosya biçimi. Başka bir uygulamada açmayı deneyin. Dosyanın görüntüsünden memnun değil misiniz? Bunun yerine başka bir uygulamada açın. Seçilen dosyayı kaydetmek için yer yok. @@ -10,10 +9,7 @@ Aygıt belleği yetersiz! Çok fazla resim var veya dosya çok büyük. Bu belge parola korumalı Yükleniyor… - Karşıya yükleniyor… Lütfen bekleyin, bu birkaç dakika sürebilir. - Karşıya yüklenen dosyalar özeldir ve 24 saat sonra otomatik olarak silinir. - Bu belgeyi açamıyoruz, çünkü biçimini desteklemiyoruz. Görüntüleyebilmeniz için geçici olarak sunucumuza yüklemek ister misiniz? Yüklenen dosyalar özeldir ve 24 saat sonra otomatik olarak silinir. Belgelerde ara Birlikte aç... Belgeyi Paylaş diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a1c726604d16..65c5795e2415 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2,7 +2,6 @@ Kötü bir şey oldu. Dosya açılamadı. Dosya bulunamadı. Belki artık mevcut değil? - Bu, desteklenen bir dosya biçimi gibi görünmüyor. Desteklenmeyen dosya biçimi. Başka bir uygulamada açmayı deneyin. Dosyanın görüntüsünden memnun değil misiniz? Bunun yerine başka bir uygulamada açın. Seçilen dosyayı kaydetmek için yer yok. @@ -10,10 +9,7 @@ Aygıt belleği yetersiz! Çok fazla resim var veya dosya çok büyük. Belge parola korumalı Yükleniyor… - Karşıya yükleniyor… Lütfen bekleyin, bu birkaç dakika sürebilir. - Karşıya yüklenen dosyalar özeldir ve 24 saat sonra otomatik olarak silinir. - Bu belgeyi açamıyoruz, çünkü biçimini desteklemiyoruz. Görüntüleyebilmeniz için geçici olarak sunucumuza yüklemek ister misiniz? Yüklenen dosyalar özeldir ve 24 saat sonra otomatik olarak silinir. Belgelerde ara Belgeyi düzenle Reklamları kaldır diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index bfdf250b2df8..bbafebff3736 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2,7 +2,6 @@ 出现了一些错误,无法打开文件。 找不到此文件。或许它已经不在那里了? - 此文件格式似乎不受支持。 不受支持的文件格式。请尝试在另一个应用程序中打开它。 对文件的显示效果不满意?在另一个应用中打开它。 没有地方保存选中的文件。 @@ -10,10 +9,7 @@ 设备内存不足!可能是因为文件中的图片太多了,也可能是文件体积过大。 此文档受密码保护。 正在加载… - 正在上传… 请稍候,可能需要几分钟。 - 被上传的文件是私密的,24 小时后文件会被自动删除。 - 我们无法打开此文档,因为本应用不支持其格式。是否要暂时将其上传到我们的服务器, 以便我们设法在未来的版本中允许本应用显示它?上传的文件依然归您所有,并在会在24小时后自动删除。 在文档中搜索 打开方式... 分享文档 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 98145f3eb642..fda944a4502e 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -2,15 +2,11 @@ 出现了一些错误,无法打开文件。 找不到此文件。或许它已经不在那里了? - 此文件格式似乎不受支持。 对文件的显示效果不满意?在另一个应用中打开它。 设备内存不足!可能是因为文件中的图片太多了,也可能是文件体积过大。 此文档受密码保护。 正在加载… - 正在上传… 请稍候,可能需要几分钟。 - 被上传的文件是私密的,24 小时后文件会被自动删除。 - 我们无法打开此文档,因为本应用不支持其格式。是否要暂时将其上传到我们的服务器, 以便我们设法在未来的版本中允许本应用显示它?上传的文件依然归您所有,并在会在24小时后自动删除。 在文档中搜索 编辑文档 移除广告 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a7fff312a820..080be51434e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,19 +5,14 @@ Couldn\'t open this file The file may be damaged, or something went wrong on our side. If you think the file is fine, tell us at support@opendocument.app Couldn\'t find file. Maybe it doesn\'t exist any longer? - This doesn\'t seem to be a supported file format. Unsupported file format. Try opening it in another app. Not happy with how the file is displayed? Open it in another app instead. No place to save the file to chosen. File could not be saved. Please contact support@opendocument.app - Upload failed. Try opening the file in another app. Device out of memory! Too many pictures, or file too big. This document is password-protected Loading… - Uploading… Please wait, this could take a few minutes. - Uploaded files are private and automatically deleted after 24 hours. - We aren\'t able to open this document, because we don\'t support its format. Do you want to upload it to our server temporarily, so we can display it for you anyway? Uploaded files are private and automatically deleted after 24 hours. Search in document Open with... Share document @@ -52,7 +47,6 @@ Next Previous Save - Upload Contact Edit your document below and press Save By default this app only offers to open document files. If another app like \"Samsung My Files\" won\'t let you open a document here, turn this on to register for all file types. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92f1d183a665..15d8bdf30d6d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ googleJavaFormat = "1.35.0" ktfmt = "0.64" # odrcore's JNI bindings, java and native in one AAR, published from OpenDocument.core -odrCore = "6.4.0" +odrCore = "6.5.0" androidxAnnotation = "1.10.0" androidxAppcompat = "1.7.1" From 2050a0a95dd71a20081ccfc72a1de40f2cdfb536 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 10 Aug 2026 21:40:58 +0200 Subject: [PATCH 2/4] Read a retired loader name out of saved state as the core Saved instance state outlives an app update, and DocumentFragment restores a parcelled Result from it. One written by the shipped version can name RAW or ONLINE, which valueOf raises IllegalArgumentException on - inside the getParcelable meant to bring the document back, where no caller can catch it. CORE is the right answer for both: what those two loaded is the core's now. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CAJTcj3dfmn1yC5TpqdCeb --- .../droid/background/FileLoader.kt | 19 +++++++++-- .../droid/background/LoaderTypeTest.kt | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt diff --git a/app/src/main/java/app/opendocument/droid/background/FileLoader.kt b/app/src/main/java/app/opendocument/droid/background/FileLoader.kt index 06e3ff7b1975..f92cda785795 100644 --- a/app/src/main/java/app/opendocument/droid/background/FileLoader.kt +++ b/app/src/main/java/app/opendocument/droid/background/FileLoader.kt @@ -118,7 +118,22 @@ abstract class FileLoader(context: Context?, protected val type: LoaderType) { enum class LoaderType { CORE, - METADATA, + METADATA; + + companion object { + + /** + * The type a parcelled [Result] names, or [CORE] for one this version no longer has. + * + * Saved instance state outlives an app update, so a bundle written before the raw and + * online loaders were dropped still names them - and `valueOf` would throw on that, + * inside the `getParcelable` that is meant to bring the document back. [CORE] is the + * right answer for both: what those two loaded is the core's now, so a reload of the + * restored result goes where it would go anyway. + */ + fun ofParcelled(name: String?): LoaderType = + entries.firstOrNull { it.name == name } ?: CORE + } } /** @@ -219,7 +234,7 @@ abstract class FileLoader(context: Context?, protected val type: LoaderType) { override fun createFromParcel(parcel: Parcel): Result { // in the order writeToParcel wrote them val classLoader = Result::class.java.classLoader - val loaderType = LoaderType.valueOf(parcel.readString()!!) + val loaderType = LoaderType.ofParcelled(parcel.readString()) val options = parcel.readParcelable(classLoader)!! return Result(loaderType, options).also { diff --git a/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt b/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt new file mode 100644 index 000000000000..9315d80aaeba --- /dev/null +++ b/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt @@ -0,0 +1,33 @@ +package app.opendocument.droid.background + +import app.opendocument.droid.background.FileLoader.LoaderType +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * [LoaderType.ofParcelled] is what stands between a saved [FileLoader.Result] written by an older + * version and the `IllegalArgumentException` `valueOf` would raise on it. Saved instance state + * outlives an app update, so the names below really do arrive. + */ +class LoaderTypeTest { + + @Test + fun aNameThisVersionStillHasIsItself() { + assertEquals(LoaderType.CORE, LoaderType.ofParcelled("CORE")) + assertEquals(LoaderType.METADATA, LoaderType.ofParcelled("METADATA")) + } + + /** The two that were dropped when odrcore learned to render svg and xml itself. */ + @Test + fun aRetiredLoaderReadsAsTheCore() { + assertEquals(LoaderType.CORE, LoaderType.ofParcelled("RAW")) + assertEquals(LoaderType.CORE, LoaderType.ofParcelled("ONLINE")) + } + + @Test + fun nothingAtAllReadsAsTheCore() { + assertEquals(LoaderType.CORE, LoaderType.ofParcelled(null)) + assertEquals(LoaderType.CORE, LoaderType.ofParcelled("")) + assertEquals(LoaderType.CORE, LoaderType.ofParcelled("core")) + } +} From d0b5ed7533f4970bd5e6df76dad50d0b0c5554ae Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 10 Aug 2026 21:47:28 +0200 Subject: [PATCH 3/4] Trim the comments back to what the code needs Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CAJTcj3dfmn1yC5TpqdCeb --- CLAUDE.md | 11 +++-------- .../opendocument/droid/background/CoreLoaderTest.kt | 5 +---- .../opendocument/droid/test/SupportedFormatsTest.kt | 9 +++------ .../app/opendocument/droid/background/FileLoader.kt | 9 ++------- .../opendocument/droid/background/LoaderService.kt | 7 ++----- .../java/app/opendocument/droid/ui/widget/PageView.kt | 3 +-- .../opendocument/droid/background/LoaderTypeTest.kt | 7 +------ 7 files changed, 13 insertions(+), 38 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b2d3017efb60..7739a6f09925 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -156,14 +156,9 @@ other way round - `mimeTypesOf` lowercases what it stores. ### There is nothing after `CoreLoader` -The app used to route around the core twice: `RawLoader` handed svg and xml straight to the -WebView, and `OnlineLoader` uploaded to use.opendocument.app or a third party viewer what -neither could open. odrcore 6.5 renders svg and xml itself, and both are gone. - -So the only answer to a file the core cannot open is to say so - `onUnsupported`, the reopen -bar and the contact dialog. Do not add a route around the core back: a format the app should -open is a format odrcore should learn, and rtf and WordPerfect are on that list. Nothing in -the app makes an outbound request for a document any more, and no document leaves the device. +The only answer to a file the core cannot open is to say so - `onUnsupported`, the reopen bar +and the contact dialog. Do not add a route around it: a format the app should open is a format +odrcore should learn, and rtf and WordPerfect are on that list. No document leaves the device. ### `text/plain` from the core is a guess unless a charset came with it diff --git a/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt b/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt index 3c75dfa4faee..c144305f2be5 100644 --- a/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt +++ b/app/src/androidTest/java/app/opendocument/droid/background/CoreLoaderTest.kt @@ -113,10 +113,7 @@ class CoreLoaderTest { assertTrue(isSupported("text/comma-separated-values")) } - /** - * 6.5 puts a decoder behind the name it gave xml in 6.3, which is what let the raw loader go: - * svg above and xml here were the two formats it existed for. - */ + /** 6.5 puts a decoder behind the name the core gave xml in 6.3. */ @Test fun xmlIsSupported() { assertTrue(isSupported("application/xml")) diff --git a/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt b/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt index f312e0f054e2..1058857a2b9b 100644 --- a/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt +++ b/app/src/androidTest/java/app/opendocument/droid/test/SupportedFormatsTest.kt @@ -84,12 +84,9 @@ class SupportedFormatsTest { } /** - * Everything the app offers itself for reaches [CoreLoader], which is the only loader that - * renders anything. - * - * Claiming a mime type nobody loads is the "cannot open" the user gets on a file they picked us - * for. The app claims from the core's own table, so this asks that the two ways of reading that - * table - `CLAIMED_FILE_TYPES` and `CORE_FILE_TYPES` - cannot come apart. + * Everything the app offers itself for reaches [CoreLoader]. Claiming a mime type nothing loads + * is the "cannot open" the user gets on a file they picked us for, so `CLAIMED_FILE_TYPES` and + * `CORE_FILE_TYPES` must not come apart. */ @Test fun everythingTheAppClaimsIsLoadedByTheCore() { diff --git a/app/src/main/java/app/opendocument/droid/background/FileLoader.kt b/app/src/main/java/app/opendocument/droid/background/FileLoader.kt index f92cda785795..d5470c48db7d 100644 --- a/app/src/main/java/app/opendocument/droid/background/FileLoader.kt +++ b/app/src/main/java/app/opendocument/droid/background/FileLoader.kt @@ -123,13 +123,8 @@ abstract class FileLoader(context: Context?, protected val type: LoaderType) { companion object { /** - * The type a parcelled [Result] names, or [CORE] for one this version no longer has. - * - * Saved instance state outlives an app update, so a bundle written before the raw and - * online loaders were dropped still names them - and `valueOf` would throw on that, - * inside the `getParcelable` that is meant to bring the document back. [CORE] is the - * right answer for both: what those two loaded is the core's now, so a reload of the - * restored result goes where it would go anyway. + * The type a parcelled [Result] names. Saved state outlives an app update and can name + * a loader this version does not have, which `valueOf` would throw on. */ fun ofParcelled(name: String?): LoaderType = entries.firstOrNull { it.name == name } ?: CORE diff --git a/app/src/main/java/app/opendocument/droid/background/LoaderService.kt b/app/src/main/java/app/opendocument/droid/background/LoaderService.kt index 52a3af437209..582049e92a1b 100644 --- a/app/src/main/java/app/opendocument/droid/background/LoaderService.kt +++ b/app/src/main/java/app/opendocument/droid/background/LoaderService.kt @@ -15,11 +15,8 @@ import app.opendocument.droid.ui.activity.DocumentFragment import java.io.File /** - * Owns the two loaders and the background thread they run on, and decides what to try next when one - * of them succeeds or fails: metadata first, then the core. - * - * There is nothing after the core: what it cannot open is reported as unsupported rather than - * routed somewhere else. + * Owns the two loaders and the background thread they run on: metadata first, then the core. + * Nothing follows the core - what it cannot open is reported as unsupported. */ class LoaderService : Service(), FileLoader.FileLoaderListener { diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt b/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt index db06bd9fbd06..ce6954053d30 100644 --- a/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt +++ b/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt @@ -110,8 +110,7 @@ constructor(context: Context, attributeSet: AttributeSet?) : @Suppress("DEPRECATION") // the request based overload needs API 24 semantics override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - // everything this view shows is served from localhost, so a link out of it is - // a link out of the app + // everything shown here is served from localhost, so any link leaves the app return try { getContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) diff --git a/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt b/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt index 9315d80aaeba..01791bd290fb 100644 --- a/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt +++ b/app/src/test/java/app/opendocument/droid/background/LoaderTypeTest.kt @@ -4,11 +4,7 @@ import app.opendocument.droid.background.FileLoader.LoaderType import org.junit.Assert.assertEquals import org.junit.Test -/** - * [LoaderType.ofParcelled] is what stands between a saved [FileLoader.Result] written by an older - * version and the `IllegalArgumentException` `valueOf` would raise on it. Saved instance state - * outlives an app update, so the names below really do arrive. - */ +/** A saved [FileLoader.Result] can name a loader this version does not have. */ class LoaderTypeTest { @Test @@ -17,7 +13,6 @@ class LoaderTypeTest { assertEquals(LoaderType.METADATA, LoaderType.ofParcelled("METADATA")) } - /** The two that were dropped when odrcore learned to render svg and xml itself. */ @Test fun aRetiredLoaderReadsAsTheCore() { assertEquals(LoaderType.CORE, LoaderType.ofParcelled("RAW")) From 8f23cc803904c9a2cc318d0ad1a5fd6b038e1dee Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 10 Aug 2026 21:53:02 +0200 Subject: [PATCH 4/4] Shorten the changelog entries Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CAJTcj3dfmn1yC5TpqdCeb --- CHANGELOG.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3600dcb5199e..11b14febb4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,18 +24,10 @@ Play Console when the release is promoted. - Opening an archive lists its entries as files instead of one page of gibberish. - Embedded fonts in a PDF no longer come out as boxes or as the wrong letters. - svg, ico, jxl, jp2, psd, wmf and emf are recognised and shown as images. -- An .xml file opens as a foldable, highlighted source view, in the encoding its - declaration names, instead of as one very long line. An .svg is recognised by - what is in it rather than by what it is called. Both used to be handed to the - browser view as they were. -- Plain text reads in a quieter gutter: the line numbers line up with their - lines and stay out of a copy of the page. -- No document opens inset by a thin border any more. +- An .xml file opens properly laid out instead of as one long line. +- Smaller fixes to plain text and to the margin documents open with. - A file the app cannot open is no longer offered for upload to our conversion - service. Nothing about a document leaves the device any more: the reader shows - what the engine renders and says plainly when it cannot. .rtf and WordPerfect - were the formats this still reached; they come back when the engine reads them - itself. + service, so no document leaves your device. This affected .rtf and WordPerfect. ## 4.13.0