diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt index 4cfa840c225e..d85ff5b8e44f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt @@ -67,7 +67,6 @@ import com.ichi2.anki.model.CardStateFilter import com.ichi2.anki.noteeditor.EditCardDestination import com.ichi2.anki.noteeditor.toIntent import com.ichi2.anki.pages.AnkiServer -import com.ichi2.anki.pages.AnkiServer.Companion.LOCALHOST import com.ichi2.anki.pages.CongratsPage import com.ichi2.anki.pages.PostRequestHandler import com.ichi2.anki.preferences.sharedPrefs @@ -234,9 +233,8 @@ abstract class AbstractFlashcardViewer : @get:VisibleForTesting var cardContent: String? = null private set - private val baseUrl get() = server.baseUrl() - private val webviewDomain - get() = "$LOCALHOST:${server.listeningPort}" + private val baseUrl + get() = getMediaBaseUrl(CollectionHelper.getMediaDirectory(this).path) private var viewerUrl: String? = null private val fadeDuration = 300 @@ -995,7 +993,6 @@ abstract class AbstractFlashcardViewer : } protected open fun createWebView(): WebView { - val resourceHandler = ViewerResourceHandler(this, webviewDomain) val webView: WebView = MyWebView(this).apply { scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY with(settings) { @@ -1013,7 +1010,7 @@ abstract class AbstractFlashcardViewer : isScrollbarFadingEnabled = true // Set transparent color to prevent flashing white when night mode enabled setBackgroundColor(Color.argb(1, 0, 0, 0)) - CardViewerWebClient(resourceHandler, this@AbstractFlashcardViewer).apply { + CardViewerWebClient(this@AbstractFlashcardViewer).apply { webViewClient = this this@AbstractFlashcardViewer.webViewClient = this } @@ -2238,7 +2235,6 @@ abstract class AbstractFlashcardViewer : } protected inner class CardViewerWebClient internal constructor( - private val loader: ViewerResourceHandler?, private val onPageFinishedCallback: OnPageFinishedCallback? = null ) : WebViewClient() { private var pageFinishedFired = true @@ -2259,6 +2255,9 @@ abstract class AbstractFlashcardViewer : override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { pageRenderStopwatch.reset() pageFinishedFired = false + val script = "globalThis.ankidroid = globalThis.ankidroid || {};" + + "ankidroid.postBaseUrl = `${server.baseUrl()}`" + view?.evaluateJavascript(script, null) } override fun shouldInterceptRequest( @@ -2266,7 +2265,6 @@ abstract class AbstractFlashcardViewer : request: WebResourceRequest ): WebResourceResponse? { val url = request.url - loader!!.shouldInterceptRequest(request)?.let { return it } if (url.toString().startsWith("file://")) { url.path?.let { path -> migrationService?.migrateFileImmediately(File(path)) } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt deleted file mode 100644 index 378802bc6d73..000000000000 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2023 Brayan Oliveira - * Copyright (c) 2023 David Allison - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A - * PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - */ -package com.ichi2.anki - -import android.content.Context -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import com.ichi2.utils.AssetHelper.guessMimeType -import timber.log.Timber -import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileInputStream -import java.io.InputStream -import java.io.RandomAccessFile -import java.nio.channels.Channels - -private const val RANGE_HEADER = "Range" - -/** - * @param authority the authority of the WebView Url, e.g. `127.0.0.1:40001`. - */ -class ViewerResourceHandler(context: Context, private val authority: String) { - private val mediaDir = CollectionHelper.getMediaDirectory(context) - - fun shouldInterceptRequest(request: WebResourceRequest): WebResourceResponse? { - val url = request.url - val path = url.path - - if (request.method != "GET" || path == null || url.authority != authority) { - return null - } - if (path == "/favicon.ico") { - return WebResourceResponse(null, null, ByteArrayInputStream(ByteArray(0))) - } - - try { - val file = File(mediaDir, path) - if (!file.exists()) { - return null - } - request.requestHeaders[RANGE_HEADER]?.let { range -> - return handlePartialContent(file, range) - } - val inputStream = FileInputStream(file) - return WebResourceResponse(guessMimeType(path), null, inputStream) - } catch (e: Exception) { - Timber.d("File not found") - return null - } - } - - private fun handlePartialContent(file: File, range: String): WebResourceResponse { - val rangeHeader = RangeHeader.from(range, defaultEnd = file.length() - 1) - - val mimeType = guessMimeType(file.path) - val inputStream = file.toInputStream(rangeHeader) - val (start, end) = rangeHeader - val responseHeaders = mapOf( - "Content-Range" to "bytes $start-$end/${file.length()}", - "Accept-Range" to "bytes" - ) - return WebResourceResponse( - mimeType, - null, - 206, - "Partial Content", - responseHeaders, - inputStream - ) - } -} - -/** - * Handles the "range" header in a HTTP Request - */ -data class RangeHeader(val start: Long, val end: Long) { - companion object { - fun from(range: String, defaultEnd: Long): RangeHeader { - val numbers = range.substring("bytes=".length).split('-') - val unspecifiedEnd = numbers.getOrNull(1).isNullOrEmpty() - return RangeHeader( - start = numbers[0].toLong(), - end = if (unspecifiedEnd) defaultEnd else numbers[1].toLong() - ) - } - } -} - -fun File.toInputStream(header: RangeHeader): InputStream { - // PERF: Test to see if a custom FileInputStream + available() would be faster - val randomAccessFile = RandomAccessFile(this, "r") - return Channels.newInputStream(randomAccessFile.channel).also { - it.skip(header.start) - } -}