diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..e393f10f91 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.{kt,kts}] + +ktlint_disabled_rules=no-wildcard-imports,max-line-length + +# Imports must be ordered in lexicographic order without any empty lines in-between. +# https://github.com/pinterest/ktlint/issues/1236 +ij_kotlin_imports_layout=* diff --git a/build.gradle.kts b/build.gradle.kts index 686c82fd8d..a0f3f2d580 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,7 @@ plugins { id("com.android.library") apply false id("org.jetbrains.kotlin.android") apply false id("org.jetbrains.dokka") apply true + id("org.jlleitschuh.gradle.ktlint") apply true } allprojects { @@ -26,6 +27,14 @@ subprojects { archiveClassifier.set("sources") from("src/main/java", "src/main/resources") } + + apply(plugin = "org.jlleitschuh.gradle.ktlint") + + ktlint { + android.set(true) + disabledRules.add("no-wildcard-imports") + disabledRules.add("max-line-length") + } } tasks.register("clean", Delete::class).configure { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 61074557b5..6d0ee15772 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,5 @@ [versions] -android-gradle-plugin = "7.3.1" - androidx-activity = "1.6.1" androidx-appcompat = "1.5.1" androidx-browser = "1.4.0" @@ -146,8 +144,3 @@ navigation = ["androidx-navigation-fragment", "androidx-navigation-ui"] room = ["androidx-room-runtime", "androidx-room-ktx"] test-frameworks = ["junit", "androidx-ext-junit", "androidx-expresso-core", "robolectric", "kotlin-junit", "assertj", "kotlinx-coroutines-test"] -[plugins] -agpapp = { id = "com.android.application", version.ref = "android-gradle-plugin" } -agplib = { id = "com.android.library", version.ref = "android-gradle-plugin" } -kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file diff --git a/readium/adapters/pdfium/pdfium-document/src/main/java/org/readium/adapters/pdfium/document/PdfiumDocument.kt b/readium/adapters/pdfium/pdfium-document/src/main/java/org/readium/adapters/pdfium/document/PdfiumDocument.kt index 9460741db3..d654bf1024 100644 --- a/readium/adapters/pdfium/pdfium-document/src/main/java/org/readium/adapters/pdfium/document/PdfiumDocument.kt +++ b/readium/adapters/pdfium/pdfium-document/src/main/java/org/readium/adapters/pdfium/document/PdfiumDocument.kt @@ -9,7 +9,10 @@ package org.readium.adapters.pdfium.document import android.content.Context import android.graphics.Bitmap import android.os.ParcelFileDescriptor +import com.shockwave.pdfium.PdfDocument as _PdfiumDocument import com.shockwave.pdfium.PdfiumCore +import java.io.File +import kotlin.reflect.KClass import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.readium.r2.shared.PdfSupport @@ -20,9 +23,6 @@ import org.readium.r2.shared.util.pdf.PdfDocument import org.readium.r2.shared.util.pdf.PdfDocumentFactory import org.readium.r2.shared.util.use import timber.log.Timber -import java.io.File -import kotlin.reflect.KClass -import com.shockwave.pdfium.PdfDocument as _PdfiumDocument @OptIn(PdfSupport::class) class PdfiumDocument( @@ -84,7 +84,7 @@ class PdfiumDocumentFactory(context: Context) : PdfDocumentFactory = PdfiumDocument::class - private val core by lazy { PdfiumCore(context.applicationContext ) } + private val core by lazy { PdfiumCore(context.applicationContext) } override suspend fun open(file: File, password: String?): PdfiumDocument = core.fromFile(file, password) diff --git a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDefaults.kt b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDefaults.kt index a42a98a18d..0515064caa 100644 --- a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDefaults.kt +++ b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDefaults.kt @@ -6,8 +6,8 @@ package org.readium.adapters.pdfium.navigator -import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.navigator.preferences.ReadingProgression +import org.readium.r2.shared.ExperimentalReadiumApi /** * Default values for the PDF navigator with the PDFium adapter. diff --git a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDocumentFragment.kt b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDocumentFragment.kt index 3d876847de..3b34dd3ee6 100644 --- a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDocumentFragment.kt +++ b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumDocumentFragment.kt @@ -13,6 +13,7 @@ import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.github.barteksc.pdfviewer.PDFView +import kotlin.math.roundToInt import kotlinx.coroutines.launch import org.readium.adapters.pdfium.document.PdfiumDocumentFactory import org.readium.r2.navigator.pdf.PdfDocumentFragment @@ -24,7 +25,6 @@ import org.readium.r2.shared.fetcher.Resource import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Publication import timber.log.Timber -import kotlin.math.roundToInt @ExperimentalReadiumApi class PdfiumDocumentFragment internal constructor( @@ -55,7 +55,11 @@ class PdfiumDocumentFragment internal constructor( private var isReloading: Boolean = false private var hasToReload: Int? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = PDFView(inflater.context, null) .also { pdfView = it } diff --git a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumPreferencesEditor.kt b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumPreferencesEditor.kt index 445188653b..842e6f6216 100644 --- a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumPreferencesEditor.kt +++ b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumPreferencesEditor.kt @@ -86,7 +86,7 @@ class PdfiumPreferencesEditor internal constructor( val scrollAxis: EnumPreference = EnumPreferenceDelegate( - getValue = { preferences.scrollAxis}, + getValue = { preferences.scrollAxis }, getEffectiveValue = { state.settings.scrollAxis }, getIsEffective = { true }, updateValue = { value -> updateValues { it.copy(scrollAxis = value) } }, diff --git a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumSettingsResolver.kt b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumSettingsResolver.kt index d33635edb9..cc821d6a0c 100644 --- a/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumSettingsResolver.kt +++ b/readium/adapters/pdfium/pdfium-navigator/src/main/java/org/readium/adapters/pdfium/navigator/PdfiumSettingsResolver.kt @@ -6,12 +6,12 @@ package org.readium.adapters.pdfium.navigator -import org.readium.r2.shared.publication.ReadingProgression as PublicationReadingProgression import org.readium.r2.navigator.preferences.Axis import org.readium.r2.navigator.preferences.Fit import org.readium.r2.navigator.preferences.ReadingProgression import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.shared.publication.Metadata +import org.readium.r2.shared.publication.ReadingProgression as PublicationReadingProgression @ExperimentalReadiumApi internal class PdfiumSettingsResolver( diff --git a/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/PsPdfKitDocument.kt b/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/PsPdfKitDocument.kt index cb5299c15b..2e231dcf9e 100644 --- a/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/PsPdfKitDocument.kt +++ b/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/PsPdfKitDocument.kt @@ -13,7 +13,10 @@ import com.pspdfkit.annotations.actions.GoToAction import com.pspdfkit.document.DocumentSource import com.pspdfkit.document.OutlineElement import com.pspdfkit.document.PageBinding +import com.pspdfkit.document.PdfDocument as _PsPdfKitDocument import com.pspdfkit.document.PdfDocumentLoader +import java.io.File +import kotlin.reflect.KClass import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.readium.r2.shared.fetcher.Resource @@ -21,9 +24,6 @@ import org.readium.r2.shared.publication.ReadingProgression import org.readium.r2.shared.util.pdf.PdfDocument import org.readium.r2.shared.util.pdf.PdfDocumentFactory import timber.log.Timber -import java.io.File -import kotlin.reflect.KClass -import com.pspdfkit.document.PdfDocument as _PsPdfKitDocument class PsPdfKitDocumentFactory(context: Context) : PdfDocumentFactory { private val context = context.applicationContext diff --git a/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/ResourceDataProvider.kt b/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/ResourceDataProvider.kt index eecd02d0f6..fcef729023 100644 --- a/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/ResourceDataProvider.kt +++ b/readium/adapters/pspdfkit/pspdfkit-document/src/main/java/org/readium/adapters/pspdfkit/document/ResourceDataProvider.kt @@ -7,13 +7,13 @@ package org.readium.adapters.pspdfkit.document import com.pspdfkit.document.providers.DataProvider +import java.util.* import kotlinx.coroutines.runBlocking import org.readium.r2.shared.fetcher.Resource import org.readium.r2.shared.fetcher.synchronized import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.isLazyInitialized import timber.log.Timber -import java.util.* class ResourceDataProvider( resource: Resource, @@ -57,4 +57,4 @@ class ResourceDataProvider( runBlocking { resource.close() } } } -} \ No newline at end of file +} diff --git a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt index 9f2d658103..f302faec1d 100644 --- a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt +++ b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitDocumentFragment.kt @@ -30,6 +30,7 @@ import com.pspdfkit.listeners.DocumentListener import com.pspdfkit.listeners.OnPreparePopupToolbarListener import com.pspdfkit.ui.PdfFragment import com.pspdfkit.ui.toolbar.popup.PdfTextSelectionPopupToolbar +import kotlin.math.roundToInt import org.readium.adapters.pspdfkit.document.PsPdfKitDocument import org.readium.r2.navigator.pdf.PdfDocumentFragment import org.readium.r2.navigator.preferences.Axis @@ -39,7 +40,6 @@ import org.readium.r2.navigator.preferences.Spread import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.services.isProtected -import kotlin.math.roundToInt @ExperimentalReadiumApi internal class PsPdfKitDocumentFragment( @@ -61,7 +61,11 @@ internal class PsPdfKitDocumentFragment( private lateinit var pdfFragment: PdfFragment private val psPdfKitListener = PsPdfKitListener() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = FragmentContainerView(inflater.context) .apply { id = R.id.readium_pspdfkit_fragment @@ -171,7 +175,13 @@ internal class PsPdfKitDocumentFragment( return center?.let { listener.onTap(it) } ?: false } - override fun onPageClick(document: PdfDocument, pageIndex: Int, event: MotionEvent?, pagePosition: PointF?, clickedAnnotation: Annotation?): Boolean { + override fun onPageClick( + document: PdfDocument, + pageIndex: Int, + event: MotionEvent?, + pagePosition: PointF?, + clickedAnnotation: Annotation? + ): Boolean { if ( pagePosition == null || clickedAnnotation is LinkAnnotation || clickedAnnotation is SoundAnnotation @@ -227,7 +237,7 @@ internal class PsPdfKitDocumentFragment( private val Spread.pageLayout: PageLayoutMode get() = when (this) { Spread.AUTO -> PageLayoutMode.AUTO - Spread.ALWAYS-> PageLayoutMode.DOUBLE + Spread.ALWAYS -> PageLayoutMode.DOUBLE Spread.NEVER -> PageLayoutMode.SINGLE } } diff --git a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitEngineProvider.kt b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitEngineProvider.kt index ef8fe1b876..16fcc80743 100644 --- a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitEngineProvider.kt +++ b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitEngineProvider.kt @@ -61,7 +61,7 @@ class PsPdfKitEngineProvider( SimplePresentation( readingProgression = settings.readingProgression, scroll = settings.scroll, - axis = if (settings.scroll) settings.scrollAxis else Axis.HORIZONTAL + axis = if (settings.scroll) settings.scrollAxis else Axis.HORIZONTAL ) override fun createPreferenceEditor( diff --git a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitPreferencesEditor.kt b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitPreferencesEditor.kt index 98c8d956a9..3e92c97911 100644 --- a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitPreferencesEditor.kt +++ b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitPreferencesEditor.kt @@ -36,7 +36,7 @@ class PsPdfKitPreferencesEditor internal constructor( val pageSpacingRange: ClosedRange = 0.0..50.0, val pageSpacingProgression: ProgressionStrategy = DoubleIncrement(5.0), ) - + private data class State( val preferences: PsPdfKitPreferences, val settings: PsPdfKitSettings @@ -68,7 +68,7 @@ class PsPdfKitPreferencesEditor internal constructor( SwitchPreferenceDelegate( getValue = { preferences.offsetFirstPage }, getEffectiveValue = { state.settings.offsetFirstPage }, - getIsEffective = { !state.settings.scroll && state.settings.spread != Spread.NEVER}, + getIsEffective = { !state.settings.scroll && state.settings.spread != Spread.NEVER }, updateValue = { value -> updateValues { it.copy(offsetFirstPage = value) } }, ) @@ -122,7 +122,7 @@ class PsPdfKitPreferencesEditor internal constructor( val newPreferences = updater(preferences) state = newPreferences.toState() } - + private fun PsPdfKitPreferences.toState() = State(preferences = this, settings = settingsResolver.settings(this)) } diff --git a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitSettingsResolver.kt b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitSettingsResolver.kt index f73fe113a0..a157f18728 100644 --- a/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitSettingsResolver.kt +++ b/readium/adapters/pspdfkit/pspdfkit-navigator/src/main/java/org/readium/adapters/pspdfkit/navigator/PsPdfKitSettingsResolver.kt @@ -8,8 +8,8 @@ package org.readium.adapters.pspdfkit.navigator import org.readium.r2.navigator.preferences.Axis import org.readium.r2.navigator.preferences.Fit -import org.readium.r2.navigator.preferences.Spread import org.readium.r2.navigator.preferences.ReadingProgression +import org.readium.r2.navigator.preferences.Spread import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.shared.publication.Metadata import org.readium.r2.shared.publication.ReadingProgression as PublicationReadingProgression diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpAuthenticating.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpAuthenticating.kt index 072f3521f0..96a9ebc4a7 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpAuthenticating.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpAuthenticating.kt @@ -44,8 +44,12 @@ interface LcpAuthenticating { * @param sender Free object that can be used by reading apps to give some UX context when * presenting dialogs. */ - suspend fun retrievePassphrase(license: AuthenticatedLicense, reason: AuthenticationReason, allowUserInteraction: Boolean, sender: Any? = null): String? - + suspend fun retrievePassphrase( + license: AuthenticatedLicense, + reason: AuthenticationReason, + allowUserInteraction: Boolean, + sender: Any? = null + ): String? enum class AuthenticationReason { @@ -94,10 +98,8 @@ interface LcpAuthenticating { val user: User? get() = document.user } - } - @Deprecated("Renamed to `LcpAuthenticating`", replaceWith = ReplaceWith("LcpAuthenticating")) typealias LCPAuthenticating = LcpAuthenticating diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt index b3d2ad21b7..5bf5ff7595 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtection.kt @@ -40,7 +40,7 @@ internal class LcpContentProtection( ?: this.authentication val license = lcpService - .retrieveLicense(asset.file, authentication, allowUserInteraction, sender) + .retrieveLicense(asset.file, authentication, allowUserInteraction, sender) val serviceFactory = LcpContentProtectionService .createFactory(license?.getOrNull(), license?.exceptionOrNull()) @@ -55,5 +55,4 @@ internal class LcpContentProtection( return Try.success(protectedFile) } - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtectionService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtectionService.kt index d7384aa930..71eedd8890 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtectionService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpContentProtectionService.kt @@ -26,11 +26,9 @@ class LcpContentProtectionService(val license: LcpLicense?, override val error: companion object { - fun createFactory(license: LcpLicense?, error: LcpException?): (Publication.Service.Context) -> LcpContentProtectionService = + fun createFactory(license: LcpLicense?, error: LcpException?): (Publication.Service.Context) -> LcpContentProtectionService = { LcpContentProtectionService(license, error) } - } - } /** diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt index 90dac2090d..6aff0c21cd 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptor.kt @@ -9,6 +9,7 @@ package org.readium.r2.lcp +import java.io.IOException import org.readium.r2.shared.extensions.coerceFirstNonNegative import org.readium.r2.shared.extensions.inflate import org.readium.r2.shared.extensions.requireLengthFitInt @@ -17,7 +18,6 @@ import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.encryption.encryption import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.getOrElse -import java.io.IOException /** * Decrypts a resource protected with LCP. @@ -78,7 +78,7 @@ internal class LcpDecryptor(val license: LcpLicense?) { if (::_length.isInitialized) return _length - _length = resource.length().flatMapCatching { length -> + _length = resource.length().flatMapCatching { length -> if (length < 2 * AES_BLOCK_SIZE) { throw Exception("Invalid CBC-encrypted stream") } @@ -91,8 +91,8 @@ internal class LcpDecryptor(val license: LcpLicense?) { check(decryptedBytes.size == AES_BLOCK_SIZE) return@mapCatching length - - AES_BLOCK_SIZE - // Minus IV - decryptedBytes.last().toInt() // Minus padding size + AES_BLOCK_SIZE - // Minus IV + decryptedBytes.last().toInt() // Minus padding size } } @@ -101,7 +101,7 @@ internal class LcpDecryptor(val license: LcpLicense?) { override suspend fun read(range: LongRange?): ResourceTry { if (range == null) - return license.decryptFully(resource.read(), isDeflated = false) + return license.decryptFully(resource.read(), isDeflated = false) @Suppress("NAME_SHADOWING") val range = range @@ -130,10 +130,10 @@ internal class LcpDecryptor(val license: LcpLicense?) { val rangeLength = if (lastBlockRead) - // use decrypted length to ensure range.last doesn't exceed decrypted length - 1 + // use decrypted length to ensure range.last doesn't exceed decrypted length - 1 range.last.coerceAtMost(length().getOrThrow() - 1) - range.first + 1 else - // the last block won't be read, so there's no need to compute length + // the last block won't be read, so there's no need to compute length range.last - range.first + 1 // keep only enough bytes to fit the length corrected request in order to never include padding @@ -152,14 +152,14 @@ internal class LcpDecryptor(val license: LcpLicense?) { } } -private suspend fun LcpLicense.decryptFully(data: ResourceTry, isDeflated: Boolean): ResourceTry = +private suspend fun LcpLicense.decryptFully(data: ResourceTry, isDeflated: Boolean): ResourceTry = data.mapCatching { encryptedData -> // Decrypts the resource. var bytes = decrypt(encryptedData) .getOrElse { throw Exception("Failed to decrypt the resource", it) } if (bytes.isEmpty()) - throw IllegalStateException("Lcp.nativeDecrypt returned an empty ByteArray") + throw IllegalStateException("Lcp.nativeDecrypt returned an empty ByteArray") // Removes the padding. val padding = bytes.last().toInt() @@ -184,4 +184,3 @@ private fun Long.ceilMultipleOf(divisor: Long) = private fun Long.floorMultipleOf(divisor: Long) = divisor * (this / divisor) - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptorTest.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptorTest.kt index eeac84e6d8..299d4681ae 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptorTest.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpDecryptorTest.kt @@ -9,6 +9,7 @@ package org.readium.r2.lcp +import kotlin.math.ceil import org.readium.r2.shared.extensions.coerceIn import org.readium.r2.shared.fetcher.Resource import org.readium.r2.shared.fetcher.mapCatching @@ -16,7 +17,6 @@ import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.use import timber.log.Timber -import kotlin.math.ceil suspend fun Publication.checkDecryption() { @@ -84,13 +84,15 @@ private suspend fun checkExceedingRangesAreAllowed(publication: Publication) { publication.get(link).use { resource -> val length = resource.length().getOrThrow() val fullTruth = resource.read().getOrThrow() - for (range in listOf( - 0 until length + 100, - 0 until length + 2048, - length - 500 until length + 200, - length until length + 5028, - length + 200 until length + 500 - )) { + for ( + range in listOf( + 0 until length + 100, + 0 until length + 2048, + length - 500 until length + 200, + length until length + 5028, + length + 200 until length + 500 + ) + ) { resource.read(range) .onFailure { throw IllegalStateException("unable to decrypt range $range from ${link.href}") @@ -108,11 +110,15 @@ private suspend fun checkExceedingRangesAreAllowed(publication: Publication) { } } -private suspend fun Resource.readByChunks(chunkSize: Long, groundTruth: ByteArray, shuffle: Boolean = true) = +private suspend fun Resource.readByChunks( + chunkSize: Long, + groundTruth: ByteArray, + shuffle: Boolean = true +) = length().mapCatching { length -> - val blockNb = ceil(length / chunkSize.toDouble()).toInt() + val blockNb = ceil(length / chunkSize.toDouble()).toInt() val blocks = (0 until blockNb) - .map { Pair(it, it * chunkSize until kotlin.math.min(length, (it + 1) * chunkSize)) } + .map { Pair(it, it * chunkSize until kotlin.math.min(length, (it + 1) * chunkSize)) } .toMutableList() if (blocks.size > 1 && shuffle) { @@ -127,12 +133,12 @@ private suspend fun Resource.readByChunks(chunkSize: Long, groundTruth: ByteArra val decryptedBytes = read(it.second).getOrElse { error -> throw IllegalStateException("unable to decrypt chunk ${it.second} from ${link().href}", error) } - check(decryptedBytes.isNotEmpty()) { "empty decrypted bytearray"} - check(decryptedBytes.contentEquals(groundTruth.sliceArray(it.second.map(Long::toInt)))) - { Timber.d("decrypted length: ${decryptedBytes.size}") + check(decryptedBytes.isNotEmpty()) { "empty decrypted bytearray" } + check(decryptedBytes.contentEquals(groundTruth.sliceArray(it.second.map(Long::toInt)))) { + Timber.d("decrypted length: ${decryptedBytes.size}") Timber.d("expected length: ${groundTruth.sliceArray(it.second.map(Long::toInt)).size}") "decrypted chunk ${it.first}: ${it.second} seems to be wrong in ${link().href}" } Pair(it.first, decryptedBytes) } - } \ No newline at end of file + } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpException.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpException.kt index dce5480ee1..0f35992c47 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpException.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpException.kt @@ -8,13 +8,23 @@ package org.readium.r2.lcp import androidx.annotation.PluralsRes import androidx.annotation.StringRes -import org.readium.r2.shared.UserException import java.net.SocketTimeoutException import java.util.* +import org.readium.r2.shared.UserException -sealed class LcpException(userMessageId: Int, vararg args: Any, quantity: Int? = null, cause: Throwable? = null) : UserException(userMessageId, quantity, *args, cause = cause) { +sealed class LcpException( + userMessageId: Int, + vararg args: Any, + quantity: Int? = null, + cause: Throwable? = null +) : UserException(userMessageId, quantity, *args, cause = cause) { constructor(@StringRes userMessageId: Int, vararg args: Any, cause: Throwable? = null) : this(userMessageId, *args, quantity = null, cause = cause) - constructor(@PluralsRes userMessageId: Int, quantity: Int, vararg args: Any, cause: Throwable? = null) : this(userMessageId, *args, quantity = quantity, cause = cause) + constructor( + @PluralsRes userMessageId: Int, + quantity: Int, + vararg args: Any, + cause: Throwable? = null + ) : this(userMessageId, *args, quantity = quantity, cause = cause) /** The interaction is not available with this License. */ object LicenseInteractionNotAvailable : LcpException(R.string.r2_lcp_exception_license_interaction_not_available) @@ -63,8 +73,8 @@ sealed class LcpException(userMessageId: Int, vararg args: Any, quantity: Int? = * in the status document. If no event is logged in the status document, no such message should * appear (certainly not "The license was registered by 0 devices"). */ - class Revoked(val date: Date, val devicesCount: Int) - : LicenseStatus(R.plurals.r2_lcp_exception_license_status_revoked, devicesCount, date, devicesCount) + class Revoked(val date: Date, val devicesCount: Int) : + LicenseStatus(R.plurals.r2_lcp_exception_license_status_revoked, devicesCount, date, devicesCount) } /** @@ -123,7 +133,6 @@ sealed class LcpException(userMessageId: Int, vararg args: Any, quantity: Int? = /** Invalid URL for link with [rel]. */ class Url(val rel: String) : Parsing() - } /** @@ -158,7 +167,6 @@ sealed class LcpException(userMessageId: Int, vararg args: Any, quantity: Int? = object InvalidLicenseSignature : LicenseIntegrity(R.string.r2_lcp_exception_license_integrity_invalid_license_signature) object InvalidUserKeyCheck : LicenseIntegrity(R.string.r2_lcp_exception_license_integrity_invalid_user_key_check) - } sealed class Decryption(@StringRes userMessageId: Int) : LcpException(userMessageId) { @@ -166,7 +174,6 @@ sealed class LcpException(userMessageId: Int, vararg args: Any, quantity: Int? = object ContentKeyDecryptError : Decryption(R.string.r2_lcp_exception_decryption_content_key_decrypt_error) object ContentDecryptError : Decryption(R.string.r2_lcp_exception_decryption_content_decrypt_error) - } companion object { @@ -179,7 +186,6 @@ sealed class LcpException(userMessageId: Int, vararg args: Any, quantity: Int? = } } - @Deprecated("Renamed to `LcpException`", replaceWith = ReplaceWith("LcpException")) typealias LCPError = LcpException diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpLicense.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpLicense.kt index e1499b066c..e8ca3acc08 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpLicense.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpLicense.kt @@ -6,6 +6,8 @@ package org.readium.r2.lcp +import java.net.URL +import java.util.* import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -16,8 +18,6 @@ import org.readium.r2.lcp.license.model.StatusDocument import org.readium.r2.shared.publication.services.ContentProtectionService import org.readium.r2.shared.util.Try import timber.log.Timber -import java.net.URL -import java.util.* /** * Opened license, used to decipher a protected publication and manage its license. @@ -80,7 +80,6 @@ interface LcpLicense : ContentProtectionService.UserRights { */ suspend fun decrypt(data: ByteArray): Try - /** * UX delegate for the loan renew LSD interaction. * @@ -104,7 +103,6 @@ interface LcpLicense : ContentProtectionService.UserRights { * web page is dismissed by the user. */ suspend fun openWebPage(url: URL) - } @Deprecated("Use `license.encryption.profile` instead", ReplaceWith("license.encryption.profile")) @@ -121,7 +119,11 @@ interface LcpLicense : ContentProtectionService.UserRights { suspend fun renewLoan(end: DateTime?, urlPresenter: suspend (URL) -> Unit): Try = Try.success(Unit) @Deprecated("Use `renewLoan` with `RenewListener` instead", ReplaceWith("renewLoan(LcpLicense.RenewListener)"), level = DeprecationLevel.ERROR) - fun renewLoan(end: DateTime?, present: (URL, dismissed: () -> Unit) -> Unit, completion: (LcpException?) -> Unit) {} + fun renewLoan( + end: DateTime?, + present: (URL, dismissed: () -> Unit) -> Unit, + completion: (LcpException?) -> Unit + ) {} @Deprecated("Use `returnPublication()` with coroutines instead", ReplaceWith("returnPublication")) @DelicateCoroutinesApi @@ -130,7 +132,6 @@ interface LcpLicense : ContentProtectionService.UserRights { completion(returnPublication().exceptionOrNull()) } } - } @Deprecated("Renamed to `LcpService`", replaceWith = ReplaceWith("LcpService")) diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpService.kt index 115d673705..1c099f16b7 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/LcpService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/LcpService.kt @@ -10,13 +10,21 @@ package org.readium.r2.lcp import android.content.Context +import java.io.File import kotlinx.coroutines.* import org.readium.r2.lcp.auth.LcpDialogAuthentication import org.readium.r2.lcp.persistence.LcpDatabase -import org.readium.r2.lcp.service.* +import org.readium.r2.lcp.service.CRLService +import org.readium.r2.lcp.service.DeviceRepository +import org.readium.r2.lcp.service.DeviceService +import org.readium.r2.lcp.service.LcpClient +import org.readium.r2.lcp.service.LicensesRepository +import org.readium.r2.lcp.service.LicensesService +import org.readium.r2.lcp.service.NetworkService +import org.readium.r2.lcp.service.PassphrasesRepository +import org.readium.r2.lcp.service.PassphrasesService import org.readium.r2.shared.publication.ContentProtection import org.readium.r2.shared.util.Try -import java.io.File /** * Service used to acquire and open publications protected with LCP. @@ -119,13 +127,15 @@ interface LcpService { @Deprecated("Use `LcpService()` instead", ReplaceWith("LcpService(context)"), level = DeprecationLevel.ERROR) fun create(context: Context): LcpService? = invoke(context) - } - @Deprecated("Use `acquirePublication()` with coroutines instead", ReplaceWith("acquirePublication(lcpl)")) @DelicateCoroutinesApi - fun importPublication(lcpl: ByteArray, authentication: LcpAuthenticating?, completion: (AcquiredPublication?, LcpException?) -> Unit) { + fun importPublication( + lcpl: ByteArray, + authentication: LcpAuthenticating?, + completion: (AcquiredPublication?, LcpException?) -> Unit + ) { GlobalScope.launch { acquirePublication(lcpl) .onSuccess { completion(it, null) } @@ -135,7 +145,11 @@ interface LcpService { @Deprecated("Use `retrieveLicense()` with coroutines instead", ReplaceWith("retrieveLicense(File(publication), authentication, allowUserInteraction = true)")) @DelicateCoroutinesApi - fun retrieveLicense(publication: String, authentication: LcpAuthenticating?, completion: (LcpLicense?, LcpException?) -> Unit) { + fun retrieveLicense( + publication: String, + authentication: LcpAuthenticating?, + completion: (LcpLicense?, LcpException?) -> Unit + ) { GlobalScope.launch { val result = retrieveLicense(File(publication), authentication ?: LcpDialogAuthentication(), allowUserInteraction = true) if (result == null) { @@ -147,10 +161,8 @@ interface LcpService { } } } - } - @Deprecated("Renamed to `LcpService()`", replaceWith = ReplaceWith("LcpService(context)")) fun R2MakeLCPService(context: Context): LcpService = LcpService(context) ?: throw Exception("liblcp is missing on the classpath") diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/MaterialRenewListener.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/MaterialRenewListener.kt index 1759cc268e..5661a01aaa 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/MaterialRenewListener.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/MaterialRenewListener.kt @@ -12,12 +12,12 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.browser.customtabs.CustomTabsIntent import androidx.fragment.app.FragmentManager import com.google.android.material.datepicker.* -import kotlinx.coroutines.suspendCancellableCoroutine import java.net.URL import java.util.* import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.suspendCancellableCoroutine /** * A default implementation of the [LcpLicense.RenewListener] using Chrome Custom Tabs for @@ -42,18 +42,24 @@ class MaterialRenewListener( val end = maximumDate?.time MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(CalendarConstraints.Builder().apply { - // Restricts the choice between the license expiration date and the given - // maximumDate. - setStart(start) - if (end != null) { - setEnd(end) - } - setValidator(CompositeDateValidator.allOf(listOfNotNull( - DateValidatorPointForward.from(start), - end?.let { DateValidatorPointBackward.before(end) } - ))) - }.build()) + .setCalendarConstraints( + CalendarConstraints.Builder().apply { + // Restricts the choice between the license expiration date and the given + // maximumDate. + setStart(start) + if (end != null) { + setEnd(end) + } + setValidator( + CompositeDateValidator.allOf( + listOfNotNull( + DateValidatorPointForward.from(start), + end?.let { DateValidatorPointBackward.before(end) } + ) + ) + ) + }.build() + ) .setSelection(start) .build() .apply { @@ -82,6 +88,4 @@ class MaterialRenewListener( private val webPageLauncher = caller.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { webPageContinuation?.resume(Unit) } - } - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpDialogAuthentication.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpDialogAuthentication.kt index f70ae3e394..83b81ab99f 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpDialogAuthentication.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpDialogAuthentication.kt @@ -24,6 +24,9 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout +import java.util.* +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.readium.r2.lcp.LcpAuthenticating @@ -32,9 +35,6 @@ import org.readium.r2.lcp.license.model.components.Link import org.readium.r2.shared.extensions.tryOr import org.readium.r2.shared.extensions.tryOrNull import timber.log.Timber -import java.util.* -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine /** * An [LcpAuthenticating] implementation presenting a dialog to the user. @@ -54,16 +54,20 @@ class LcpDialogAuthentication : LcpAuthenticating { if (allowUserInteraction) withContext(Dispatchers.Main) { askPassphrase(license, reason, sender) } else null - private suspend fun askPassphrase(license: LcpAuthenticating.AuthenticatedLicense, reason: LcpAuthenticating.AuthenticationReason, sender: Any?): String? { + private suspend fun askPassphrase( + license: LcpAuthenticating.AuthenticatedLicense, + reason: LcpAuthenticating.AuthenticationReason, + sender: Any? + ): String? { val hostView = (sender as? View) ?: (sender as? Activity)?.findViewById(android.R.id.content)?.getChildAt(0) ?: (sender as? Fragment)?.view - ?: run { - Timber.e("No valid [sender] was passed to `LcpDialogAuthentication::retrievePassphrase()`. Make sure it is an Activity, a Fragment or a View.") - return null - } + ?: run { + Timber.e("No valid [sender] was passed to `LcpDialogAuthentication::retrievePassphrase()`. Make sure it is an Activity, a Fragment or a View.") + return null + } val context = hostView.context val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - @SuppressLint("InflateParams") // https://stackoverflow.com/q/26404951/1474476 + @SuppressLint("InflateParams") // https://stackoverflow.com/q/26404951/1474476 val dialogView = inflater.inflate(R.layout.r2_lcp_auth_dialog, null) val title = dialogView.findViewById(R.id.r2_title) as TextView @@ -152,9 +156,10 @@ class LcpDialogAuthentication : LcpAuthenticating { else -> Intent(Intent.ACTION_VIEW) } - startActivity(Intent(action).apply { - data = url - }) + startActivity( + Intent(action).apply { + data = url + } + ) } - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpPassphraseAuthentication.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpPassphraseAuthentication.kt index de84f4710e..84f87b0462 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpPassphraseAuthentication.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/auth/LcpPassphraseAuthentication.kt @@ -31,5 +31,4 @@ class LcpPassphraseAuthentication( return passphrase } - -} \ No newline at end of file +} diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt index 784c5802bd..faef7f453a 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/License.kt @@ -9,6 +9,9 @@ package org.readium.r2.lcp.license +import java.net.HttpURLConnection +import java.util.* +import kotlin.time.ExperimentalTime import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -28,9 +31,6 @@ import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.mediatype.MediaType import timber.log.Timber -import java.net.HttpURLConnection -import java.util.* -import kotlin.time.ExperimentalTime @OptIn(ExperimentalTime::class) internal class License( @@ -56,7 +56,6 @@ internal class License( val decryptedData = LcpClient.decrypt(context, data) Try.success(decryptedData) } - } catch (e: Exception) { Try.failure(LcpException.wrap(e)) } @@ -209,11 +208,9 @@ internal class License( validateStatusDocument(data) return Try.success(documents.license.rights.end) - } catch (e: CancellationException) { // Passthrough for cancelled coroutines throw e - } catch (e: Exception) { return Try.failure(LcpException.wrap(e)) } @@ -245,7 +242,6 @@ internal class License( } return Try.success(Unit) - } catch (e: Exception) { return Try.failure(LcpException.wrap(e)) } @@ -261,7 +257,4 @@ internal class License( private fun validateStatusDocument(data: ByteArray): Unit = validation.validate(LicenseValidation.Document.status(data)) { _, _ -> } - } - - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/LicenseValidation.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/LicenseValidation.kt index 7ce0df0c49..a918497a76 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/LicenseValidation.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/LicenseValidation.kt @@ -9,6 +9,9 @@ package org.readium.r2.lcp.license +import java.util.* +import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime import kotlinx.coroutines.runBlocking import org.readium.r2.lcp.BuildConfig.DEBUG import org.readium.r2.lcp.LcpAuthenticating @@ -20,9 +23,6 @@ import org.readium.r2.lcp.service.* import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.mediatype.MediaType import timber.log.Timber -import java.util.* -import kotlin.time.Duration.Companion.seconds -import kotlin.time.ExperimentalTime internal sealed class Either { class Left(val left: A) : Either() @@ -42,7 +42,11 @@ internal enum class ObserverPolicy { always } -internal data class ValidatedDocuments constructor(val license: LicenseDocument, private val context: Context, val status: StatusDocument? = null) { +internal data class ValidatedDocuments constructor( + val license: LicenseDocument, + private val context: Context, + val status: StatusDocument? = null +) { fun getContext(): LcpClient.Context { when (context) { is Either.Left -> return context.left @@ -59,14 +63,17 @@ internal sealed class State { data class fetchLicense(val license: LicenseDocument, val status: StatusDocument) : State() data class checkLicenseStatus(val license: LicenseDocument, val status: StatusDocument?) : State() data class retrievePassphrase(val license: LicenseDocument, val status: StatusDocument?) : State() - data class validateIntegrity(val license: LicenseDocument, val status: StatusDocument?, val passphrase: String) : State() + data class validateIntegrity( + val license: LicenseDocument, + val status: StatusDocument?, + val passphrase: String + ) : State() data class registerDevice(val documents: ValidatedDocuments, val link: Link) : State() data class valid(val documents: ValidatedDocuments) : State() data class failure(val error: Exception) : State() object cancelled : State() } - internal sealed class Event { data class retrievedLicenseData(val data: ByteArray) : Event() data class validatedLicense(val license: LicenseDocument) : Event() @@ -183,10 +190,10 @@ internal class LicenseValidation( } state { on { - it.error?.let{ error -> + it.error?.let { error -> if (DEBUG) Timber.d("State.valid(ValidatedDocuments(license, Either.Right(error), status))") transitionTo(State.valid(ValidatedDocuments(license, Either.Right(error), status))) - }?: run { + } ?: run { if (DEBUG) Timber.d("State.requestPassphrase(license, status)") transitionTo(State.retrievePassphrase(license, status)) } @@ -225,7 +232,7 @@ internal class LicenseValidation( } state { on { - it.statusData?.let { statusData-> + it.statusData?.let { statusData -> if (DEBUG) Timber.d("State.validateStatus(documents.license, statusData)") transitionTo(State.validateStatus(documents.license, statusData)) } ?: run { @@ -394,7 +401,11 @@ internal class LicenseValidation( } companion object { - fun observe(licenseValidation: LicenseValidation, policy: ObserverPolicy = ObserverPolicy.always, observer: Observer) { + fun observe( + licenseValidation: LicenseValidation, + policy: ObserverPolicy = ObserverPolicy.always, + observer: Observer + ) { var notified = true when (licenseValidation.stateMachine.state) { is State.valid -> observer((licenseValidation.stateMachine.state as State.valid).documents, null) @@ -408,5 +419,4 @@ internal class LicenseValidation( observers.add(Pair(observer, policy)) } } - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/StateMachine.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/StateMachine.kt index a828fe6997..6a2b7df110 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/StateMachine.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/StateMachine.kt @@ -12,7 +12,7 @@ package org.readium.r2.lcp.license import java.util.concurrent.atomic.AtomicReference internal class StateMachine private constructor( - private val graph: Graph + private val graph: Graph ) { private val stateRef = AtomicReference(graph.initialState) @@ -58,10 +58,10 @@ internal class StateMachine private constructor( } private fun STATE.getDefinition() = graph.stateDefinitions - .filter { it.key.matches(this) } - .map { it.value } - .firstOrNull() - .let { checkNotNull(it) } + .filter { it.key.matches(this) } + .map { it.value } + .firstOrNull() + .let { checkNotNull(it) } private fun STATE.notifyOnEnter(cause: EVENT) { getDefinition().onEnterListeners.forEach { it(this, cause) } @@ -81,21 +81,21 @@ internal class StateMachine private constructor( abstract val event: EVENT data class Valid internal constructor( - override val fromState: STATE, - override val event: EVENT, - val toState: STATE + override val fromState: STATE, + override val event: EVENT, + val toState: STATE ) : Transition() data class Invalid internal constructor( - override val fromState: STATE, - override val event: EVENT + override val fromState: STATE, + override val event: EVENT ) : Transition() } data class Graph( - val initialState: STATE, - val stateDefinitions: Map, State>, - val onTransitionListeners: List<(Transition) -> Unit> + val initialState: STATE, + val stateDefinitions: Map, State>, + val onTransitionListeners: List<(Transition) -> Unit> ) { class State internal constructor() { @@ -104,7 +104,7 @@ internal class StateMachine private constructor( val transitions = linkedMapOf, (STATE, EVENT) -> TransitionTo>() data class TransitionTo internal constructor( - val toState: STATE + val toState: STATE ) } } @@ -132,7 +132,7 @@ internal class StateMachine private constructor( } class GraphBuilder( - graph: Graph? = null + graph: Graph? = null ) { private var initialState = graph?.initialState private val stateDefinitions = LinkedHashMap(graph?.stateDefinitions ?: emptyMap()) @@ -143,8 +143,8 @@ internal class StateMachine private constructor( } fun state( - stateMatcher: Matcher, - init: StateDefinitionBuilder.() -> Unit + stateMatcher: Matcher, + init: StateDefinitionBuilder.() -> Unit ) { stateDefinitions[stateMatcher] = StateDefinitionBuilder().apply(init).build() } @@ -153,7 +153,10 @@ internal class StateMachine private constructor( state(Matcher.any(), init) } - inline fun state(state: S, noinline init: StateDefinitionBuilder.() -> Unit) { + inline fun state( + state: S, + noinline init: StateDefinitionBuilder.() -> Unit + ) { state(Matcher.eq(state), init) } @@ -174,8 +177,8 @@ internal class StateMachine private constructor( inline fun eq(value: R): Matcher = Matcher.eq(value) fun on( - eventMatcher: Matcher, - createTransitionTo: S.(E) -> Graph.State.TransitionTo + eventMatcher: Matcher, + createTransitionTo: S.(E) -> Graph.State.TransitionTo ) { stateDefinition.transitions[eventMatcher] = { state, event -> @Suppress("UNCHECKED_CAST") @@ -184,14 +187,14 @@ internal class StateMachine private constructor( } inline fun on( - noinline createTransitionTo: S.(E) -> Graph.State.TransitionTo + noinline createTransitionTo: S.(E) -> Graph.State.TransitionTo ) { return on(any(), createTransitionTo) } inline fun on( - event: E, - noinline createTransitionTo: S.(E) -> Graph.State.TransitionTo + event: E, + noinline createTransitionTo: S.(E) -> Graph.State.TransitionTo ) { return on(eq(event), createTransitionTo) } @@ -214,7 +217,7 @@ internal class StateMachine private constructor( @Suppress("UNUSED") // The unused warning is probably a compiler bug. fun S.transitionTo(state: STATE) = - Graph.State.TransitionTo(state) + Graph.State.TransitionTo(state) @Suppress("UNUSED") // The unused warning is probably a compiler bug. fun S.dontTransition() = transitionTo(this) @@ -223,16 +226,16 @@ internal class StateMachine private constructor( companion object { fun create( - init: GraphBuilder.() -> Unit + init: GraphBuilder.() -> Unit ): StateMachine { return create(null, init) } private fun create( - graph: Graph?, - init: GraphBuilder.() -> Unit + graph: Graph?, + init: GraphBuilder.() -> Unit ): StateMachine { return StateMachine(GraphBuilder(graph).apply(init).build()) } } -} \ No newline at end of file +} diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/BytesLicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/BytesLicenseContainer.kt index 2762873c74..0692e18c46 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/BytesLicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/BytesLicenseContainer.kt @@ -16,11 +16,9 @@ import org.readium.r2.lcp.license.model.LicenseDocument */ internal class BytesLicenseContainer(private var bytes: ByteArray) : LicenseContainer { - override fun read() : ByteArray = bytes + override fun read(): ByteArray = bytes override fun write(license: LicenseDocument) { bytes = license.data } - } - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/EPUBLicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/EPUBLicenseContainer.kt index a6ae4d897f..c44bc7a0f2 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/EPUBLicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/EPUBLicenseContainer.kt @@ -9,9 +9,8 @@ package org.readium.r2.lcp.license.container - /** * Access a License Document stored in an EPUB archive, under META-INF/license.lcpl. */ -internal class EPUBLicenseContainer(epub: String) - : ZIPLicenseContainer(zip = epub, pathInZIP = "META-INF/license.lcpl") +internal class EPUBLicenseContainer(epub: String) : + ZIPLicenseContainer(zip = epub, pathInZIP = "META-INF/license.lcpl") diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LCPLLicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LCPLLicenseContainer.kt index ba269fdba0..b5dd5590aa 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LCPLLicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LCPLLicenseContainer.kt @@ -8,16 +8,16 @@ */ package org.readium.r2.lcp.license.container +import java.io.File import org.readium.r2.lcp.LcpException import org.readium.r2.lcp.license.model.LicenseDocument -import java.io.File /** * Access a License Document stored in an LCP License Document file (LCPL). */ internal class LCPLLicenseContainer(private val lcpl: String) : LicenseContainer { - override fun read() : ByteArray = + override fun read(): ByteArray = try { File(lcpl).readBytes() } catch (e: Exception) { @@ -31,6 +31,4 @@ internal class LCPLLicenseContainer(private val lcpl: String) : LicenseContainer throw LcpException.Container.WriteFailed(lcpl) } } - } - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt index 6de5e8c819..c65cc3c04d 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/LicenseContainer.kt @@ -18,11 +18,14 @@ import org.readium.r2.shared.util.mediatype.MediaType * or a standalone LCPL file). */ internal interface LicenseContainer { - fun read() : ByteArray + fun read(): ByteArray fun write(license: LicenseDocument) } -internal suspend fun createLicenseContainer(filepath: String, mediaTypes: List = emptyList()): LicenseContainer { +internal suspend fun createLicenseContainer( + filepath: String, + mediaTypes: List = emptyList() +): LicenseContainer { val mediaType = MediaType.ofFile(filepath, mediaTypes = mediaTypes, fileExtensions = emptyList()) ?: throw LcpException.Container.OpenFailed return createLicenseContainer(filepath, mediaType) diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/WebPubLicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/WebPubLicenseContainer.kt index 7e8e52f05b..c7033c224c 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/WebPubLicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/WebPubLicenseContainer.kt @@ -12,5 +12,5 @@ package org.readium.r2.lcp.license.container /** * Access a License Document stored in a Readium WebPub package (e.g. WebPub, Audiobook, LCPDF or DiViNa). */ -internal class WebPubLicenseContainer(path: String) - : ZIPLicenseContainer(zip = path, pathInZIP = "license.lcpl") +internal class WebPubLicenseContainer(path: String) : + ZIPLicenseContainer(zip = path, pathInZIP = "license.lcpl") diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/ZIPLicenseContainer.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/ZIPLicenseContainer.kt index 99b18b2c0d..a3115aa5f8 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/ZIPLicenseContainer.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/container/ZIPLicenseContainer.kt @@ -9,11 +9,11 @@ package org.readium.r2.lcp.license.container +import java.io.File +import java.util.zip.ZipFile import org.readium.r2.lcp.LcpException import org.readium.r2.lcp.license.model.LicenseDocument import org.zeroturnaround.zip.ZipUtil -import java.io.File -import java.util.zip.ZipFile /** * Access to a License Document stored in a ZIP archive. @@ -39,7 +39,6 @@ internal open class ZIPLicenseContainer(private val zip: String, private val pat } catch (e: Exception) { throw LcpException.Container.ReadFailed(pathInZIP) } - } override fun write(license: LicenseDocument) { diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/LicenseDocument.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/LicenseDocument.kt index 4308c9e2a8..0843124d27 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/LicenseDocument.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/LicenseDocument.kt @@ -9,6 +9,9 @@ package org.readium.r2.lcp.license.model +import java.net.URL +import java.nio.charset.Charset +import java.util.* import org.json.JSONObject import org.readium.r2.lcp.LcpException import org.readium.r2.lcp.license.model.components.Link @@ -21,9 +24,6 @@ import org.readium.r2.lcp.service.URLParameters import org.readium.r2.shared.extensions.iso8601ToDate import org.readium.r2.shared.extensions.optNullableString import org.readium.r2.shared.util.mediatype.MediaType -import java.net.URL -import java.nio.charset.Charset -import java.util.* class LicenseDocument(val data: ByteArray) { val provider: String @@ -77,7 +77,7 @@ class LicenseDocument(val data: ByteArray) { fun links(rel: Rel, type: MediaType? = null): List = links.allWithRel(rel.rawValue, type) - fun url(rel: Rel, preferredType: MediaType? = null, parameters: URLParameters = emptyMap()): URL { + fun url(rel: Rel, preferredType: MediaType? = null, parameters: URLParameters = emptyMap()): URL { val link = link(rel, preferredType) ?: links.firstWithRelAndNoType(rel.rawValue) ?: throw LcpException.Parsing.Url(rel = rel.rawValue) @@ -87,5 +87,4 @@ class LicenseDocument(val data: ByteArray) { val description: String get() = "License($id)" - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/StatusDocument.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/StatusDocument.kt index b1f924902a..b15c8671f0 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/StatusDocument.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/StatusDocument.kt @@ -9,6 +9,9 @@ package org.readium.r2.lcp.license.model +import java.net.URL +import java.nio.charset.Charset +import java.util.* import org.json.JSONObject import org.readium.r2.lcp.LcpException import org.readium.r2.lcp.license.model.components.Link @@ -20,9 +23,6 @@ import org.readium.r2.shared.extensions.iso8601ToDate import org.readium.r2.shared.extensions.mapNotNull import org.readium.r2.shared.extensions.optNullableString import org.readium.r2.shared.util.mediatype.MediaType -import java.net.URL -import java.nio.charset.Charset -import java.util.* class StatusDocument(val data: ByteArray) { val id: String @@ -95,7 +95,7 @@ class StatusDocument(val data: ByteArray) { internal fun linkWithNoType(rel: Rel): Link? = links.firstWithRelAndNoType(rel.rawValue) - fun url(rel: Rel, preferredType: MediaType? = null, parameters: URLParameters = emptyMap()): URL { + fun url(rel: Rel, preferredType: MediaType? = null, parameters: URLParameters = emptyMap()): URL { val link = link(rel, preferredType) ?: linkWithNoType(rel) ?: throw LcpException.Parsing.Url(rel = rel.rawValue) @@ -104,13 +104,11 @@ class StatusDocument(val data: ByteArray) { } fun events(type: Event.EventType): List = - events(type.rawValue) + events(type.rawValue) fun events(type: String): List = - events.filter { it.type == type } + events.filter { it.type == type } val description: String get() = "Status(${status.rawValue})" - } - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Link.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Link.kt index 222c3243f3..b001bf4315 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Link.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Link.kt @@ -10,6 +10,7 @@ package org.readium.r2.lcp.license.model.components +import java.net.URL import org.json.JSONArray import org.json.JSONObject import org.readium.r2.lcp.LcpException @@ -17,7 +18,6 @@ import org.readium.r2.lcp.service.URLParameters import org.readium.r2.shared.publication.Link import org.readium.r2.shared.util.URITemplate import org.readium.r2.shared.util.mediatype.MediaType -import java.net.URL data class Link(val json: JSONObject) { val href: String @@ -54,10 +54,9 @@ data class Link(val json: JSONObject) { profile = if (json.has("profile")) json.getString("profile") else null length = if (json.has("length")) json.getInt("length") else null hash = if (json.has("hash")) json.getString("hash") else null - } - fun url(parameters: URLParameters) : URL { + fun url(parameters: URLParameters): URL { if (!templated) { return URL(href) } @@ -81,5 +80,4 @@ data class Link(val json: JSONObject) { else URITemplate(href).parameters } - -} \ No newline at end of file +} diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Links.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Links.kt index a01b9c9a3b..02fda8880b 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Links.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/Links.kt @@ -37,5 +37,4 @@ data class Links(val json: JSONArray) { this.rel.contains(rel) && (type?.matches(this.type) ?: true) operator fun get(rel: String): List = allWithRel(rel) - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/Rights.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/Rights.kt index 65d9108c20..104cdde54e 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/Rights.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/Rights.kt @@ -9,11 +9,11 @@ package org.readium.r2.lcp.license.model.components.lcp +import java.util.* import org.json.JSONObject import org.readium.r2.shared.extensions.iso8601ToDate import org.readium.r2.shared.extensions.optNullableInt import org.readium.r2.shared.extensions.optNullableString -import java.util.* data class Rights(val json: JSONObject) { val print: Int? diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/User.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/User.kt index f6575f1524..ad5a5081fc 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/User.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lcp/User.kt @@ -20,13 +20,13 @@ data class User(val json: JSONObject) { var encrypted = mutableListOf() init { - id = if (json.has("id")) json.getString("id") else null - email = if (json.has("email")) json.getString("email") else null - name = if (json.has("name")) json.getString("name") else null + id = if (json.has("id")) json.getString("id") else null + email = if (json.has("email")) json.getString("email") else null + name = if (json.has("name")) json.getString("name") else null if (json.has("encrypted")) { val encryptedArray = json.getJSONArray("encrypted") - for (i in 0 until encryptedArray.length()){ + for (i in 0 until encryptedArray.length()) { encrypted.add(encryptedArray.getString(i)) } } @@ -37,6 +37,5 @@ data class User(val json: JSONObject) { // json.remove("encrypted") extensions = json - } } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/Event.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/Event.kt index e58212703f..291c175d21 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/Event.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/Event.kt @@ -9,10 +9,10 @@ package org.readium.r2.lcp.license.model.components.lsd +import java.util.* import org.json.JSONObject import org.readium.r2.shared.extensions.iso8601ToDate import org.readium.r2.shared.extensions.optNullableString -import java.util.* data class Event(val json: JSONObject) { val type: String = json.optNullableString("type") ?: "" @@ -31,5 +31,4 @@ data class Event(val json: JSONObject) { operator fun invoke(rawValue: String) = values().firstOrNull { it.rawValue == rawValue } } } - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/PotentialRights.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/PotentialRights.kt index e724b299dd..519740b860 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/PotentialRights.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/license/model/components/lsd/PotentialRights.kt @@ -9,10 +9,10 @@ package org.readium.r2.lcp.license.model.components.lsd +import java.util.* import org.json.JSONObject import org.readium.r2.shared.extensions.iso8601ToDate import org.readium.r2.shared.extensions.optNullableString -import java.util.* data class PotentialRights(val json: JSONObject) { val end: Date? = json.optNullableString("end")?.iso8601ToDate() diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDao.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDao.kt index 4690c424a2..80f6b22975 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDao.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDao.kt @@ -47,4 +47,4 @@ interface LcpDao { @Query("UPDATE ${License.TABLE_NAME} SET ${License.RIGHTPRINT} = :quantity WHERE ${License.LICENSE_ID} = :licenseId") fun setPrintsLeft(quantity: Int, licenseId: String) -} \ No newline at end of file +} diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/Database.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDatabase.kt similarity index 94% rename from readium/lcp/src/main/java/org/readium/r2/lcp/persistence/Database.kt rename to readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDatabase.kt index b08b598c18..d877001a6c 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/Database.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/persistence/LcpDatabase.kt @@ -16,7 +16,6 @@ import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase - @Database( entities = [Passphrase::class, License::class], version = 2, @@ -46,17 +45,16 @@ internal abstract class LcpDatabase : RoomDatabase() { user_id TEXT, passphrase TEXT NOT NULL ) - """.trimIndent() + """.trimIndent() ) database.execSQL( """ INSERT INTO passphrases (license_id, provider, user_id, passphrase) SELECT id, origin, userId, passphrase FROM Transactions - """.trimIndent() + """.trimIndent() ) database.execSQL("DROP TABLE Transactions") - database.execSQL( """ CREATE TABLE new_Licenses ( @@ -66,13 +64,13 @@ internal abstract class LcpDatabase : RoomDatabase() { right_copy INTEGER, registered INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0 ) - """.trimIndent() + """.trimIndent() ) database.execSQL( """ INSERT INTO new_Licenses (license_id, right_print, right_copy, registered) SELECT id, printsLeft, copiesLeft, registered FROM Licenses - """.trimIndent() + """.trimIndent() ) database.execSQL("DROP TABLE Licenses") database.execSQL("ALTER TABLE new_Licenses RENAME TO licenses") diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/public/Deprecated.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/public/Deprecated.kt index 5b1333f469..f6b44cfa8f 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/public/Deprecated.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/public/Deprecated.kt @@ -38,4 +38,4 @@ typealias LCPError = LcpException @Deprecated("Renamed to `LcpService()`", ReplaceWith("LcpService()"), level = DeprecationLevel.ERROR) fun R2MakeLCPService(context: Context) = - LcpService(context) \ No newline at end of file + LcpService(context) diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/CRLService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/CRLService.kt index 708ad4dd46..992ca8e40b 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/CRLService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/CRLService.kt @@ -12,14 +12,14 @@ package org.readium.r2.lcp.service import android.content.Context import android.content.SharedPreferences import android.os.Build +import java.util.* +import kotlin.time.ExperimentalTime import org.joda.time.DateTime import org.joda.time.Days import org.readium.r2.lcp.BuildConfig.DEBUG import org.readium.r2.lcp.LcpException import org.readium.r2.shared.util.getOrElse import timber.log.Timber -import java.util.* -import kotlin.time.ExperimentalTime @OptIn(ExperimentalTime::class) internal class CRLService(val network: NetworkService, val context: Context) { @@ -41,7 +41,6 @@ internal class CRLService(val network: NetworkService, val context: Context) { return try { fetch() .also { saveLocal(it) } - } catch (e: Exception) { if (DEBUG) Timber.e(e) localCRL ?: throw e @@ -54,10 +53,10 @@ internal class CRLService(val network: NetworkService, val context: Context) { .getOrElse { throw LcpException.CrlFetching } return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - "-----BEGIN X509 CRL-----${Base64.getEncoder().encodeToString(data)}-----END X509 CRL-----" - } else { - "-----BEGIN X509 CRL-----${android.util.Base64.encodeToString(data, android.util.Base64.DEFAULT)}-----END X509 CRL-----" - } + "-----BEGIN X509 CRL-----${Base64.getEncoder().encodeToString(data)}-----END X509 CRL-----" + } else { + "-----BEGIN X509 CRL-----${android.util.Base64.encodeToString(data, android.util.Base64.DEFAULT)}-----END X509 CRL-----" + } } // Returns (CRL, expired) @@ -78,4 +77,3 @@ internal class CRLService(val network: NetworkService, val context: Context) { return Days.daysBetween(date, DateTime.now()).days } } - diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/DeviceService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/DeviceService.kt index da632f921b..cde7889faf 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/DeviceService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/DeviceService.kt @@ -15,15 +15,19 @@ import android.content.Context import android.content.SharedPreferences import android.content.pm.PackageManager import androidx.core.app.ActivityCompat -import org.readium.r2.lcp.license.model.LicenseDocument -import org.readium.r2.lcp.license.model.components.Link -import timber.log.Timber import java.io.Serializable import java.util.* import kotlin.time.ExperimentalTime +import org.readium.r2.lcp.license.model.LicenseDocument +import org.readium.r2.lcp.license.model.components.Link +import timber.log.Timber @OptIn(ExperimentalTime::class) -internal class DeviceService(private val repository: DeviceRepository, private val network: NetworkService, val context: Context):Serializable { +internal class DeviceService( + private val repository: DeviceRepository, + private val network: NetworkService, + val context: Context +) : Serializable { private val preferences: SharedPreferences = context.getSharedPreferences("org.readium.r2.settings", Context.MODE_PRIVATE) @@ -58,7 +62,6 @@ internal class DeviceService(private val repository: DeviceRepository, private v val asQueryParameters: URLParameters get() = mapOf("id" to id, "name" to name) - suspend fun registerLicense(license: LicenseDocument, link: Link): ByteArray? { if (repository.isDeviceRegistered(license)) { return null @@ -71,5 +74,4 @@ internal class DeviceService(private val repository: DeviceRepository, private v repository.registerDevice(license) return data } - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LcpClient.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LcpClient.kt index 01b0b11aba..f361d9a522 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LcpClient.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LcpClient.kt @@ -1,8 +1,8 @@ package org.readium.r2.lcp.service +import java.lang.reflect.InvocationTargetException import org.readium.r2.lcp.LcpException import org.readium.r2.shared.extensions.tryOr -import java.lang.reflect.InvocationTargetException internal object LcpClient { @@ -30,7 +30,7 @@ internal object LcpClient { .newInstance(hashedPassphrase, encryptedContentKey, token, profile) } - private val instance: Any by lazy { + private val instance: Any by lazy { klass.newInstance() } @@ -59,7 +59,7 @@ internal object LcpClient { klass .getMethod("decrypt", Class.forName("org.readium.lcp.sdk.DRMContext"), ByteArray::class.java) .invoke(instance, context.toDRMContext(), encryptedData) - as ByteArray + as ByteArray } catch (e: InvocationTargetException) { throw mapException(e.targetException) } @@ -104,4 +104,4 @@ internal object LcpClient { else -> LcpException.Unknown(e) } } -} \ No newline at end of file +} diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesRepository.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesRepository.kt index ff7e5cea83..7dd818c036 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesRepository.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesRepository.kt @@ -35,7 +35,7 @@ internal class LicensesRepository(private val lcpDao: LcpDao) { lcpDao.setCopiesLeft(quantity, licenseId) } - fun printsLeft(licenseId: String) : Int? { + fun printsLeft(licenseId: String): Int? { return lcpDao.getPrintsLeft(licenseId) } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt index e870e1f174..5bf9cd5dce 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/LicensesService.kt @@ -10,6 +10,8 @@ package org.readium.r2.lcp.service import android.content.Context +import java.io.File +import kotlin.coroutines.resume import kotlinx.coroutines.* import org.readium.r2.lcp.LcpAuthenticating import org.readium.r2.lcp.LcpException @@ -24,8 +26,6 @@ import org.readium.r2.shared.extensions.tryOr import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.mediatype.MediaType import timber.log.Timber -import java.io.File -import kotlin.coroutines.resume internal class LicensesService( private val licenses: LicensesRepository, @@ -51,7 +51,12 @@ internal class LicensesService( Try.failure(LcpException.wrap(e)) } - override suspend fun retrieveLicense(file: File, authentication: LcpAuthenticating, allowUserInteraction: Boolean, sender: Any?): Try? = + override suspend fun retrieveLicense( + file: File, + authentication: LcpAuthenticating, + allowUserInteraction: Boolean, + sender: Any? + ): Try? = try { val container = createLicenseContainer(file.path) // WARNING: Using the Default dispatcher in the state machine code is critical. If we were using the Main Dispatcher, @@ -66,7 +71,12 @@ internal class LicensesService( Try.failure(LcpException.wrap(e)) } - private suspend fun retrieveLicense(container: LicenseContainer, authentication: LcpAuthenticating?, allowUserInteraction: Boolean, sender: Any?): License? = + private suspend fun retrieveLicense( + container: LicenseContainer, + authentication: LcpAuthenticating?, + allowUserInteraction: Boolean, + sender: Any? + ): License? = suspendCancellableCoroutine { cont -> retrieveLicense(container, authentication, allowUserInteraction, sender) { license -> if (cont.isActive) { @@ -75,14 +85,22 @@ internal class LicensesService( } } - private fun retrieveLicense(container: LicenseContainer, authentication: LcpAuthenticating?, allowUserInteraction: Boolean, sender: Any?, completion: (License?) -> Unit) { + private fun retrieveLicense( + container: LicenseContainer, + authentication: LcpAuthenticating?, + allowUserInteraction: Boolean, + sender: Any?, + completion: (License?) -> Unit + ) { var initialData = container.read() Timber.d("license ${LicenseDocument(data = initialData).json}") - val validation = LicenseValidation(authentication = authentication, crl = this.crl, - device = this.device, network = this.network, passphrases = this.passphrases, context = this.context, - allowUserInteraction = allowUserInteraction, sender = sender) { licenseDocument -> + val validation = LicenseValidation( + authentication = authentication, crl = this.crl, + device = this.device, network = this.network, passphrases = this.passphrases, context = this.context, + allowUserInteraction = allowUserInteraction, sender = sender + ) { licenseDocument -> try { launch { this@LicensesService.licenses.addLicense(licenseDocument) @@ -102,7 +120,6 @@ internal class LicensesService( Timber.d("Failed to write updated License Document in container: $error") } } - } validation.validate(LicenseValidation.Document.license(initialData)) { documents, error -> @@ -110,8 +127,8 @@ internal class LicensesService( Timber.d("validated documents $it") try { documents.getContext() - completion( License(documents = it, validation = validation, licenses = this.licenses, device = this.device, network = this.network) ) - } catch (e:Exception) { + completion(License(documents = it, validation = validation, licenses = this.licenses, device = this.device, network = this.network)) + } catch (e: Exception) { throw e } } @@ -144,5 +161,4 @@ internal class LicensesService( suggestedFilename = "${license.id}.${mediaType.fileExtension}" ) } - } diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/NetworkService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/NetworkService.kt index 0e316b412a..182592ecaf 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/NetworkService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/NetworkService.kt @@ -10,13 +10,6 @@ package org.readium.r2.lcp.service import android.net.Uri -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.readium.r2.lcp.LcpException -import org.readium.r2.shared.util.Try -import org.readium.r2.shared.util.mediatype.MediaType -import org.readium.r2.shared.util.mediatype.sniffMediaType -import timber.log.Timber import java.io.BufferedInputStream import java.io.File import java.io.FileOutputStream @@ -25,7 +18,13 @@ import java.net.URL import kotlin.math.round import kotlin.time.Duration import kotlin.time.ExperimentalTime - +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.readium.r2.lcp.LcpException +import org.readium.r2.shared.util.Try +import org.readium.r2.shared.util.mediatype.MediaType +import org.readium.r2.shared.util.mediatype.sniffMediaType +import timber.log.Timber internal typealias URLParameters = Map @@ -41,7 +40,13 @@ internal class NetworkService { } } - suspend fun fetch(url: String, method: Method = Method.GET, parameters: URLParameters = emptyMap(), timeout: Duration? = null, headers: Map = emptyMap()): Try = + suspend fun fetch( + url: String, + method: Method = Method.GET, + parameters: URLParameters = emptyMap(), + timeout: Duration? = null, + headers: Map = emptyMap() + ): Try = withContext(Dispatchers.IO) { try { @Suppress("NAME_SHADOWING") @@ -82,7 +87,12 @@ internal class NetworkService { } } - suspend fun download(url: URL, destination: File, mediaType: String? = null, onProgress: (Double) -> Unit): MediaType? = withContext(Dispatchers.IO) { + suspend fun download( + url: URL, + destination: File, + mediaType: String? = null, + onProgress: (Double) -> Unit + ): MediaType? = withContext(Dispatchers.IO) { try { val connection = url.openConnection() as HttpURLConnection if (connection.responseCode >= 400) { @@ -123,17 +133,15 @@ internal class NetworkService { } connection.sniffMediaType(mediaTypes = listOfNotNull(mediaType)) - } catch (e: Exception) { Timber.e(e) throw LcpException.Network(e) } } - } private fun Double.roundToDecimals(decimals: Int): Double { var multiplier = 1.0 repeat(decimals) { multiplier *= 10 } return round(this * multiplier) / multiplier -} \ No newline at end of file +} diff --git a/readium/lcp/src/main/java/org/readium/r2/lcp/service/PassphrasesService.kt b/readium/lcp/src/main/java/org/readium/r2/lcp/service/PassphrasesService.kt index 7b36c3c2b4..bc9be10499 100644 --- a/readium/lcp/src/main/java/org/readium/r2/lcp/service/PassphrasesService.kt +++ b/readium/lcp/src/main/java/org/readium/r2/lcp/service/PassphrasesService.kt @@ -15,7 +15,12 @@ import org.readium.r2.lcp.license.model.LicenseDocument internal class PassphrasesService(private val repository: PassphrasesRepository) { - suspend fun request(license: LicenseDocument, authentication: LcpAuthenticating?, allowUserInteraction: Boolean, sender: Any?): String? { + suspend fun request( + license: LicenseDocument, + authentication: LcpAuthenticating?, + allowUserInteraction: Boolean, + sender: Any? + ): String? { val candidates = this@PassphrasesService.possiblePassphrasesFromRepository(license) val passphrase = try { LcpClient.findOneValidPassphrase(license.json.toString(), candidates) @@ -29,7 +34,13 @@ internal class PassphrasesService(private val repository: PassphrasesRepository) } } - private suspend fun authenticate(license: LicenseDocument, reason: LcpAuthenticating.AuthenticationReason, authentication: LcpAuthenticating, allowUserInteraction: Boolean, sender:Any?): String? { + private suspend fun authenticate( + license: LicenseDocument, + reason: LcpAuthenticating.AuthenticationReason, + authentication: LcpAuthenticating, + allowUserInteraction: Boolean, + sender: Any? + ): String? { val authenticatedLicense = LcpAuthenticating.AuthenticatedLicense(document = license) val clearPassphrase = authentication.retrievePassphrase(authenticatedLicense, reason, allowUserInteraction, sender) ?: return null @@ -79,5 +90,4 @@ internal class PassphrasesService(private val repository: PassphrasesRepository) companion object { private val sha256Regex = "^([a-f0-9]{64})$".toRegex(RegexOption.IGNORE_CASE) } - } diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/DefaultMetadataFactory.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/DefaultMetadataFactory.kt index e665c9a1cf..036693ecce 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/DefaultMetadataFactory.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/DefaultMetadataFactory.kt @@ -10,7 +10,7 @@ import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.services.cover @ExperimentalMedia2 -internal class DefaultMetadataFactory(private val publication: Publication): MediaMetadataFactory { +internal class DefaultMetadataFactory(private val publication: Publication) : MediaMetadataFactory { private val coroutineScope = CoroutineScope(Dispatchers.Default) @@ -31,15 +31,15 @@ internal class DefaultMetadataFactory(private val publication: Publication): Med authors ?.let { - builder.putString(MediaMetadata.METADATA_KEY_AUTHOR, it) - builder.putString(MediaMetadata.METADATA_KEY_ARTIST, it) + builder.putString(MediaMetadata.METADATA_KEY_AUTHOR, it) + builder.putString(MediaMetadata.METADATA_KEY_ARTIST, it) } publication.metadata.duration ?.let { builder.putLong(MediaMetadata.METADATA_KEY_DURATION, it.toLong() * 1000) } cover.await() - ?.let { builder.putBitmap(MediaMetadata.METADATA_KEY_ART, it)} + ?.let { builder.putBitmap(MediaMetadata.METADATA_KEY_ART, it) } return builder.build() } @@ -61,7 +61,7 @@ internal class DefaultMetadataFactory(private val publication: Publication): Med builder.putString(MediaMetadata.METADATA_KEY_ARTIST, it) builder.putString(MediaMetadata.METADATA_KEY_AUTHOR, it) } - cover.await()?.let { builder.putBitmap(MediaMetadata.METADATA_KEY_ART, it)} + cover.await()?.let { builder.putBitmap(MediaMetadata.METADATA_KEY_ART, it) } return builder.build() } -} \ No newline at end of file +} diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/ExoPlayerDataSource.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/ExoPlayerDataSource.kt index 8898a1dde8..16836b801b 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/ExoPlayerDataSource.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/ExoPlayerDataSource.kt @@ -13,11 +13,11 @@ import com.google.android.exoplayer2.upstream.BaseDataSource import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSpec import com.google.android.exoplayer2.upstream.TransferListener +import java.io.IOException import kotlinx.coroutines.runBlocking import org.readium.r2.shared.fetcher.Resource import org.readium.r2.shared.fetcher.buffered import org.readium.r2.shared.publication.Publication -import java.io.IOException sealed class ExoPlayerDataSourceException(message: String, cause: Throwable?) : IOException(message, cause) { class NotOpened(message: String) : ExoPlayerDataSourceException(message, null) @@ -30,7 +30,10 @@ sealed class ExoPlayerDataSourceException(message: String, cause: Throwable?) : */ class ExoPlayerDataSource internal constructor(private val publication: Publication) : BaseDataSource(/* isNetwork = */ true) { - class Factory(private val publication: Publication, private val transferListener: TransferListener? = null) : DataSource.Factory { + class Factory( + private val publication: Publication, + private val transferListener: TransferListener? = null + ) : DataSource.Factory { override fun createDataSource(): DataSource = ExoPlayerDataSource(publication).apply { @@ -38,7 +41,6 @@ class ExoPlayerDataSource internal constructor(private val publication: Publicat addTransferListener(transferListener) } } - } private data class OpenedResource( @@ -115,7 +117,6 @@ class ExoPlayerDataSource internal constructor(private val publication: Publicat openedResource.position += data.count() return data.count() - } catch (e: Exception) { if (e is InterruptedException) { return 0 @@ -143,5 +144,4 @@ class ExoPlayerDataSource internal constructor(private val publication: Publicat } openedResource = null } - } diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/LocatorHelpers.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/LocatorHelpers.kt index cc22f2397f..e62eac2cd1 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/LocatorHelpers.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/LocatorHelpers.kt @@ -6,11 +6,11 @@ package org.readium.navigator.media2 -import org.readium.r2.shared.publication.Locator import java.util.* import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.ExperimentalTime +import org.readium.r2.shared.publication.Locator // FIXME: This should be in r2-shared once this public API is specified. diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaMetadataFactory.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaMetadataFactory.kt index 483be70e0d..02ba2b3ce3 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaMetadataFactory.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaMetadataFactory.kt @@ -19,4 +19,4 @@ interface MediaMetadataFactory { * Creates the [MediaMetadata] for the reading order resource at the given [index]. */ suspend fun resourceMetadata(index: Int): MediaMetadata -} \ No newline at end of file +} diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaNavigator.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaNavigator.kt index fb96b902df..3332e877fa 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaNavigator.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaNavigator.kt @@ -17,6 +17,10 @@ import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.media2.SessionPlayerConnector import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.upstream.DataSource +import java.util.concurrent.Executors +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel @@ -34,10 +38,6 @@ import org.readium.r2.shared.publication.indexOfFirstWithHref import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.flatMap import timber.log.Timber -import java.util.concurrent.Executors -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -import kotlin.time.ExperimentalTime /** * An audiobook navigator to connect to a MediaSession from Jetpack Media2. @@ -176,7 +176,7 @@ class MediaNavigator private constructor( ) } - private suspend fun executeCommand(block: suspend () -> T): T { + private suspend fun executeCommand(block: suspend () -> T): T { val result = commandMutex.withLock { preventStateUpdate = true val result = block() @@ -311,9 +311,8 @@ class MediaNavigator private constructor( fun session(context: Context, activityIntent: PendingIntent, id: String? = null): MediaSession = playerFacade.session(context, id, activityIntent) - data class Configuration( - val positionRefreshRate: Double = 2.0, // Hz + val positionRefreshRate: Double = 2.0, // Hz val skipForwardInterval: Duration = 30.seconds, val skipBackwardInterval: Duration = 30.seconds, ) @@ -352,7 +351,7 @@ class MediaNavigator private constructor( internal val error: SessionPlayerError ) : Exception("${error.name} error occurred in SessionPlayer.") - class InvalidArgument(message: String): Exception(message) + class InvalidArgument(message: String) : Exception(message) } /* @@ -399,7 +398,7 @@ class MediaNavigator private constructor( val callbackExecutor = Executors.newSingleThreadExecutor() player.registerPlayerCallback(callbackExecutor, callback) - val facade = SessionPlayerFacade(player, seekCompletedChannel, callback.playerState) + val facade = SessionPlayerFacade(player, seekCompletedChannel, callback.playerState) return preparePlayer(publication, facade, metadataFactory) // Ignoring failure to set initial locator .onSuccess { goInitialLocator(publication, initialLocator, facade) } @@ -433,7 +432,7 @@ class MediaNavigator private constructor( .setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory)) .setAudioAttributes( AudioAttributes.Builder() - .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC ) + .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) .setUsage(C.USAGE_MEDIA) .build(), true diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/OptIn.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/OptIn.kt index 670c4d5a8a..d67319d39d 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/OptIn.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/OptIn.kt @@ -1,3 +1,5 @@ +// ktlint-disable filename + /* * Copyright 2022 Readium Foundation. All rights reserved. * Use of this source code is governed by the BSD-style license diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerCallback.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerCallback.kt index 98f8609d82..b06d0d69ba 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerCallback.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerCallback.kt @@ -9,13 +9,13 @@ package org.readium.navigator.media2 import androidx.media2.common.MediaItem import androidx.media2.common.MediaMetadata import androidx.media2.common.SessionPlayer +import kotlin.time.Duration +import kotlin.time.ExperimentalTime import kotlinx.coroutines.* import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import timber.log.Timber -import kotlin.time.Duration -import kotlin.time.ExperimentalTime @OptIn(ExperimentalTime::class) internal class SessionPlayerCallback( @@ -55,7 +55,11 @@ internal class SessionPlayerCallback( private val _playbackSpeed = MutableStateFlow(1f) - override fun onPlaylistChanged(player: SessionPlayer, list: MutableList?, metadata: MediaMetadata?) { + override fun onPlaylistChanged( + player: SessionPlayer, + list: MutableList?, + metadata: MediaMetadata? + ) { Timber.d("onPlaylistChanged") _currentItem.tryEmit(player.currentItem) diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerFacade.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerFacade.kt index b48c32a9b8..081f536af9 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerFacade.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerFacade.kt @@ -13,6 +13,12 @@ import androidx.media2.common.MediaMetadata import androidx.media2.common.SessionPlayer import androidx.media2.session.MediaSession import com.google.common.util.concurrent.SettableFuture +import java.util.concurrent.* +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.ExperimentalTime import kotlinx.coroutines.MainScope import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.ReceiveChannel @@ -23,12 +29,6 @@ import kotlinx.coroutines.flow.receiveAsFlow import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.flatMap import timber.log.Timber -import java.util.concurrent.* -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.ExperimentalTime /** * This class's purpose is two-fold: diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerHelpers.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerHelpers.kt index cc20adc939..f6895327cc 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerHelpers.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SessionPlayerHelpers.kt @@ -9,10 +9,10 @@ package org.readium.navigator.media2 import androidx.media2.common.MediaItem import androidx.media2.common.MediaMetadata import androidx.media2.common.SessionPlayer -import org.readium.r2.shared.util.Try import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.ExperimentalTime +import org.readium.r2.shared.util.Try internal enum class SessionPlayerState { Idle, @@ -60,7 +60,7 @@ internal typealias SessionPlayerResult = Try internal class SessionPlayerException(val error: SessionPlayerError) : Exception() -internal enum class SessionPlayerError{ +internal enum class SessionPlayerError { BAD_VALUE, INVALID_STATE, IO, @@ -81,7 +81,7 @@ internal enum class SessionPlayerError{ fun fromCode(resultCode: Int): SessionPlayerError { require(resultCode != 0) - return when(resultCode) { + return when (resultCode) { -3 -> BAD_VALUE -2 -> INVALID_STATE -5 -> IO @@ -130,7 +130,7 @@ internal val SessionPlayer.currentItem: ItemState } internal val SessionPlayer.playbackSpeedNullable - get() = playbackSpeed.takeUnless { it == 0f }?.toDouble() + get() = playbackSpeed.takeUnless { it == 0f }?.toDouble() internal val SessionPlayer.currentIndexNullable get() = currentMediaItemIndex.takeUnless { it == SessionPlayer.INVALID_ITEM_INDEX } @@ -170,4 +170,3 @@ internal val List.durations: List? @ExperimentalTime internal val List.metadata: List get() = map { it.metadata!! } - diff --git a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SmartSeeker.kt b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SmartSeeker.kt index b762632b50..f399f4cae3 100644 --- a/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SmartSeeker.kt +++ b/readium/navigator-media2/src/main/java/org/readium/navigator/media2/SmartSeeker.kt @@ -17,7 +17,12 @@ internal object SmartSeeker { data class Result(val index: Int, val position: Duration) - fun dispatchSeek(offset: Duration, currentPosition: Duration, currentIndex: Int, playlist: List): Result { + fun dispatchSeek( + offset: Duration, + currentPosition: Duration, + currentIndex: Int, + playlist: List + ): Result { val currentDuration = playlist[currentIndex] val dummyNewPosition = currentPosition + offset diff --git a/readium/navigator-media2/src/test/java/org/readium/navigator/media2/SmartSeekerTest.kt b/readium/navigator-media2/src/test/java/org/readium/navigator/media2/SmartSeekerTest.kt index 737e3d4676..52713327b7 100644 --- a/readium/navigator-media2/src/test/java/org/readium/navigator/media2/SmartSeekerTest.kt +++ b/readium/navigator-media2/src/test/java/org/readium/navigator/media2/SmartSeekerTest.kt @@ -1,10 +1,10 @@ package org.readium.navigator.media2 -import org.junit.Assert.assertEquals -import org.junit.Test import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlin.time.ExperimentalTime +import org.junit.Assert.assertEquals +import org.junit.Test @OptIn(ExperimentalTime::class) class SmartSeekerTest { @@ -104,4 +104,4 @@ class SmartSeekerTest { ) assertEquals(SmartSeeker.Result(0, 0.seconds), result) } -} \ No newline at end of file +} diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/DecorableNavigator.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/DecorableNavigator.kt index 04a7c1d5a9..4798609188 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/DecorableNavigator.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/DecorableNavigator.kt @@ -12,6 +12,7 @@ import android.os.Parcelable import androidx.annotation.ColorInt import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback +import kotlin.reflect.KClass import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -20,7 +21,6 @@ import org.json.JSONObject import org.readium.r2.shared.JSONable import org.readium.r2.shared.extensions.JSONParceler import org.readium.r2.shared.publication.Locator -import kotlin.reflect.KClass /** * A navigator able to render arbitrary decorations over a publication. @@ -44,7 +44,7 @@ interface DecorableNavigator : Navigator { * particular feature before enabling it. For example, underlining an audiobook does not make * sense, so an Audiobook Navigator would not support the `underline` decoration style. */ - fun supportsDecorationStyle(style: KClass): Boolean + fun supportsDecorationStyle(style: KClass): Boolean /** * Registers a new [listener] for decoration interactions in the given [group]. @@ -114,9 +114,15 @@ data class Decoration( */ interface Style : Parcelable { @Parcelize - data class Highlight(@ColorInt override val tint: Int, override val isActive: Boolean = false) : Style, Tinted, Activable + data class Highlight( + @ColorInt override val tint: Int, + override val isActive: Boolean = false + ) : Style, Tinted, Activable @Parcelize - data class Underline(@ColorInt override val tint: Int, override val isActive: Boolean = false) : Style, Tinted, Activable + data class Underline( + @ColorInt override val tint: Int, + override val isActive: Boolean = false + ) : Style, Tinted, Activable /** A type of [Style] which has a tint color. */ interface Tinted { @@ -167,9 +173,9 @@ suspend fun List.changesByHref(target: List): Map Unit = } } - /** * A navigator rendering an audio or video publication. */ diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt index 4a199effc6..9ddf2629e6 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/R2BasicWebView.kt @@ -24,6 +24,8 @@ import android.widget.ListPopupWindow import android.widget.PopupWindow import android.widget.TextView import androidx.annotation.RequiresApi +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -44,9 +46,6 @@ import org.readium.r2.shared.publication.ReadingProgression import org.readium.r2.shared.util.Href import org.readium.r2.shared.util.use import timber.log.Timber -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - @OptIn(ExperimentalDecorator::class) open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebView(context, attrs) { @@ -93,8 +92,8 @@ open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebView(conte var resourceUrl: String? = null internal val scrollModeFlow = MutableStateFlow(false) - - /** Indicates that a user text selection is active. */ + +/** Indicates that a user text selection is active. */ internal var isSelecting = false val scrollMode: Boolean get() = scrollModeFlow.value @@ -119,7 +118,6 @@ open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebView(conte } progression - } else { var x = scrollX.toDouble() val pageWidth = computeHorizontalScrollExtent() @@ -496,7 +494,6 @@ open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebView(conte } } - fun Boolean.toInt() = if (this) 1 else 0 fun scrollToStart() { @@ -572,7 +569,7 @@ open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebView(conte fun runJavaScript(javascript: String, callback: ((String) -> Unit)? = null) { if (BuildConfig.DEBUG) { val filename = URLUtil.guessFileName(url, null, null) - Timber.d("runJavaScript in ${filename}: $javascript") + Timber.d("runJavaScript in $filename: $javascript") } this.evaluateJavascript(javascript) { result -> @@ -639,7 +636,10 @@ open class R2BasicWebView(context: Context, attrs: AttributeSet) : WebView(conte } @RequiresApi(Build.VERSION_CODES.M) - inner class Callback2Wrapper(val callback: ActionMode.Callback, val callback2: ActionMode.Callback2?) : ActionMode.Callback by callback, ActionMode.Callback2() { + inner class Callback2Wrapper( + val callback: ActionMode.Callback, + val callback2: ActionMode.Callback2? + ) : ActionMode.Callback by callback, ActionMode.Callback2() { override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: Rect?) = callback2?.onGetContentRect(mode, view, outRect) ?: super.onGetContentRect(mode, view, outRect) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/R2WebView.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/R2WebView.kt index 38c3f0e53e..4c5029b173 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/R2WebView.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/R2WebView.kt @@ -20,12 +20,12 @@ import androidx.annotation.CallSuper import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import kotlin.math.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.readium.r2.navigator.BuildConfig.DEBUG import timber.log.Timber -import kotlin.math.* /** * Created by Aferdita Muriqi on 12/2/17. @@ -46,7 +46,7 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, if (mCurItem < numPages - 1) { mCurItem++ url?.let { - listener.onPageChanged(mCurItem + 1, numPages, it) + listener.onPageChanged(mCurItem + 1, numPages, it) } } } @@ -59,7 +59,7 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, if (mCurItem > 0) { mCurItem-- url?.let { - listener.onPageChanged(mCurItem + 1, numPages, it) + listener.onPageChanged(mCurItem + 1, numPages, it) } } } @@ -71,9 +71,8 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, private val MIN_DISTANCE_FOR_FLING = 25 // dips private val MIN_FLING_VELOCITY = 400 // dips - override fun getContentHeight(): Int { - return this.computeVerticalScrollRange() //working after load of page + return this.computeVerticalScrollRange() // working after load of page } internal class ItemInfo { @@ -92,7 +91,7 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, private val mTempRect = Rect() - internal var mCurItem: Int = 0 // Index of currently displayed page. + internal var mCurItem: Int = 0 // Index of currently displayed page. private var mScroller: Scroller? = null private var mIsScrollStarted: Boolean = false @@ -206,61 +205,75 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, mCloseEnough = (CLOSE_ENOUGH * density).toInt() if (ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - ViewCompat.setImportantForAccessibility(this, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + ViewCompat.setImportantForAccessibility( + this, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES + ) } - ViewCompat.setOnApplyWindowInsetsListener(this, - object : androidx.core.view.OnApplyWindowInsetsListener { - private val mTempRect = Rect() - - override fun onApplyWindowInsets(v: View, - originalInsets: WindowInsetsCompat): WindowInsetsCompat { - // First let the ViewPager itself try and consume them... - val applied = ViewCompat.onApplyWindowInsets(v, originalInsets) - if (applied.isConsumed) { - // If the ViewPager consumed all insets, return now - return applied - } - - // Now we'll manually dispatch the insets to our children. Since ViewPager - // children are always full-height, we do not want to use the standard - // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them, - // the rest of the children will not receive any insets. To workaround this - // we manually dispatch the applied insets, not allowing children to - // consume them from each other. We do however keep track of any insets - // which are consumed, returning the union of our children's consumption - val res = mTempRect - val insets = applied.getInsets(WindowInsetsCompat.Type.systemBars()) - res.left = insets.left - res.top = insets.top - res.right = insets.right - res.bottom = insets.bottom - - var i = 0 - val count = childCount - while (i < count) { - val childInsets = ViewCompat - .dispatchApplyWindowInsets(getChildAt(i), applied).getInsets(WindowInsetsCompat.Type.systemBars()) - // Now keep track of any consumed by tracking each dimension's min - // value - res.left = min(childInsets.left, - res.left) - res.top = min(childInsets.top, - res.top) - res.right = min(childInsets.right, - res.right) - res.bottom = min(childInsets.bottom, - res.bottom) - i++ - } + ViewCompat.setOnApplyWindowInsetsListener( + this, + object : androidx.core.view.OnApplyWindowInsetsListener { + private val mTempRect = Rect() + + override fun onApplyWindowInsets( + v: View, + originalInsets: WindowInsetsCompat + ): WindowInsetsCompat { + // First let the ViewPager itself try and consume them... + val applied = ViewCompat.onApplyWindowInsets(v, originalInsets) + if (applied.isConsumed) { + // If the ViewPager consumed all insets, return now + return applied + } - // Now return a new WindowInsets, using the consumed window insets - return WindowInsetsCompat.Builder(applied) - .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(res.left, res.top, res.right, res.bottom)) - .build() + // Now we'll manually dispatch the insets to our children. Since ViewPager + // children are always full-height, we do not want to use the standard + // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them, + // the rest of the children will not receive any insets. To workaround this + // we manually dispatch the applied insets, not allowing children to + // consume them from each other. We do however keep track of any insets + // which are consumed, returning the union of our children's consumption + val res = mTempRect + val insets = applied.getInsets(WindowInsetsCompat.Type.systemBars()) + res.left = insets.left + res.top = insets.top + res.right = insets.right + res.bottom = insets.bottom + + var i = 0 + val count = childCount + while (i < count) { + val childInsets = ViewCompat + .dispatchApplyWindowInsets(getChildAt(i), applied).getInsets(WindowInsetsCompat.Type.systemBars()) + // Now keep track of any consumed by tracking each dimension's min + // value + res.left = min( + childInsets.left, + res.left + ) + res.top = min( + childInsets.top, + res.top + ) + res.right = min( + childInsets.right, + res.right + ) + res.bottom = min( + childInsets.bottom, + res.bottom + ) + i++ } - }) + + // Now return a new WindowInsets, using the consumed window insets + return WindowInsetsCompat.Builder(applied) + .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.of(res.left, res.top, res.right, res.bottom)) + .build() + } + } + ) } override fun onDetachedFromWindow() { @@ -341,11 +354,9 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, if (post) { url?.let { - listener.onPageChanged(item + 1, numPages, it) + listener.onPageChanged(item + 1, numPages, it) } } - - } // We want the duration of the page snap animation to be influenced by the distance that @@ -359,7 +370,6 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, return sin(float.toDouble()).toFloat() } - /** * Like [View.scrollBy], but scroll smoothly instead of immediately. * @@ -493,8 +503,10 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, childLeft = paddingLeft paddingLeft += child.measuredWidth } - Gravity.CENTER_HORIZONTAL -> childLeft = max((width - child.measuredWidth) / 2, - paddingLeft) + Gravity.CENTER_HORIZONTAL -> childLeft = max( + (width - child.measuredWidth) / 2, + paddingLeft + ) Gravity.END -> { childLeft = width - paddingRight - child.measuredWidth paddingRight += child.measuredWidth @@ -506,8 +518,10 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, childTop = paddingTop paddingTop += child.measuredHeight } - Gravity.CENTER_VERTICAL -> childTop = max((height - child.measuredHeight) / 2, - paddingTop) + Gravity.CENTER_VERTICAL -> childTop = max( + (height - child.measuredHeight) / 2, + paddingTop + ) Gravity.BOTTOM -> { childTop = height - paddingBottom - child.measuredHeight paddingBottom += child.measuredHeight @@ -515,9 +529,11 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, else -> childTop = paddingTop } childLeft += scrollX - child.layout(childLeft, childTop, - childLeft + child.measuredWidth, - childTop + child.measuredHeight) + child.layout( + childLeft, childTop, + childLeft + child.measuredWidth, + childTop + child.measuredHeight + ) decorCount++ } } @@ -572,7 +588,8 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, onPageScrolled(currentPage, pageOffset, offsetPixels) if (!mCalledSuper) { throw IllegalStateException( - "onPageScrolled did not call superclass implementation") + "onPageScrolled did not call superclass implementation" + ) } return true } @@ -611,8 +628,10 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, childLeft = paddingLeft paddingLeft += child.width } - Gravity.CENTER_HORIZONTAL -> childLeft = max((width - child.measuredWidth) / 2, - paddingLeft) + Gravity.CENTER_HORIZONTAL -> childLeft = max( + (width - child.measuredWidth) / 2, + paddingLeft + ) Gravity.END -> { childLeft = width - paddingRight - child.measuredWidth paddingRight += child.measuredWidth @@ -631,7 +650,6 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, mCalledSuper = true } - private fun completeScroll(postEvents: Boolean) { val needPopulate = mScrollState == SCROLL_STATE_SETTLING if (needPopulate) { @@ -776,7 +794,6 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, return super.onTouchEvent(ev) } - /** * @return Info about the page at the current scroll position. * This can be synthetic for a missing middle page; the 'object' field can be null. @@ -828,7 +845,12 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, return lastItem } - private fun determineTargetPage(currentPage: Int, initialVelocity: Int, currentVelocity: Int, deltaX: Int): Int { + private fun determineTargetPage( + currentPage: Int, + initialVelocity: Int, + currentVelocity: Int, + deltaX: Int + ): Int { // If the initialVelocity and currentVelocity don't have the same sign, it means the user // reversed the drag direction. In which case we consider this as a cancellation. val isCancelled = (initialVelocity * currentVelocity) <= 0 @@ -841,7 +863,6 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, } } - private fun onSecondaryPointerUp(ev: MotionEvent) { val pointerIndex = ev.actionIndex val pointerId = ev.getPointerId(pointerIndex) @@ -946,8 +967,10 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, var handled = false - val nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, - direction) + val nextFocused = FocusFinder.getInstance().findNextFocus( + this, currentFocused, + direction + ) if (nextFocused != null && nextFocused !== currentFocused) { if (direction == View.FOCUS_LEFT) { // If there is nothing to the left, or this is causing us to @@ -1052,7 +1075,6 @@ class R2WebView(context: Context, attrs: AttributeSet) : R2BasicWebView(context, */ var gravity: Int = 0 - /** * Adapter position this view is for if !isDecor */ diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/SelectableNavigator.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/SelectableNavigator.kt index a16e401f4c..4e60a87a8c 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/SelectableNavigator.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/SelectableNavigator.kt @@ -31,4 +31,4 @@ interface SelectableNavigator : Navigator { data class Selection( val locator: Locator, val rect: RectF?, -) \ No newline at end of file +) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/SimplePresentation.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/SimplePresentation.kt index f6743fff59..c9b217eefc 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/SimplePresentation.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/SimplePresentation.kt @@ -7,9 +7,9 @@ package org.readium.r2.navigator import org.readium.r2.navigator.preferences.Axis +import org.readium.r2.navigator.preferences.ReadingProgression import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.shared.InternalReadiumApi -import org.readium.r2.navigator.preferences.ReadingProgression @InternalReadiumApi @OptIn(ExperimentalReadiumApi::class) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/audio/PublicationDataSource.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/audio/PublicationDataSource.kt index 37bbf183df..5ac5dad6e0 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/audio/PublicationDataSource.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/audio/PublicationDataSource.kt @@ -13,11 +13,11 @@ import com.google.android.exoplayer2.upstream.BaseDataSource import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSpec import com.google.android.exoplayer2.upstream.TransferListener +import java.io.IOException import kotlinx.coroutines.runBlocking import org.readium.r2.shared.fetcher.Resource import org.readium.r2.shared.fetcher.buffered import org.readium.r2.shared.publication.Publication -import java.io.IOException internal sealed class PublicationDataSourceException(message: String, cause: Throwable?) : IOException(message, cause) { class NotOpened(message: String) : PublicationDataSourceException(message, null) @@ -30,7 +30,10 @@ internal sealed class PublicationDataSourceException(message: String, cause: Thr */ internal class PublicationDataSource(private val publication: Publication) : BaseDataSource(/* isNetwork = */ true) { - class Factory(private val publication: Publication, private val transferListener: TransferListener? = null) : DataSource.Factory { + class Factory( + private val publication: Publication, + private val transferListener: TransferListener? = null + ) : DataSource.Factory { override fun createDataSource(): DataSource = PublicationDataSource(publication).apply { @@ -38,7 +41,6 @@ internal class PublicationDataSource(private val publication: Publication) : Bas addTransferListener(transferListener) } } - } private data class OpenedResource( @@ -115,7 +117,6 @@ internal class PublicationDataSource(private val publication: Publication) : Bas openedResource.position += data.count() return data.count() - } catch (e: Exception) { if (e is InterruptedException) { return 0 @@ -138,5 +139,4 @@ internal class PublicationDataSource(private val publication: Publication) : Bas } openedResource = null } - } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt index c733715521..c609cca669 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt @@ -11,6 +11,8 @@ import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle +import java.util.concurrent.TimeUnit +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -26,8 +28,6 @@ import org.readium.r2.shared.extensions.getPublication import org.readium.r2.shared.publication.* import org.readium.r2.shared.publication.services.isRestricted import timber.log.Timber -import java.util.concurrent.TimeUnit -import kotlin.coroutines.CoroutineContext open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activity, MediaPlayerCallback, VisualNavigator { @@ -102,7 +102,7 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit } override val readingProgression: ReadingProgression - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + get() = TODO("not implemented") // To change initializer of created properties use File | Settings | File Templates. @ExperimentalReadiumApi override val presentation: StateFlow @@ -156,95 +156,97 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit mediaPlayer = R2MediaPlayer(readingOrderOverHttp, this) Handler(mainLooper).postDelayed({ - - if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { - if (!loadedInitialLocator) { - go(publication.readingOrder.first()) - } + if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { - binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - /** - * Notification that the progress level has changed. Clients can use the fromUser parameter - * to distinguish user-initiated changes from those that occurred programmatically. - * - * @param seekBar The SeekBar whose progress has changed - * @param progress The current progress level. This will be in the range min..max where min - * @param fromUser True if the progress change was initiated by the user. - */ - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - if (!fromUser) { - return - } - mediaPlayer.seekTo(progress) - if (DEBUG) Timber.d("progress $progress") + if (!loadedInitialLocator) { + go(publication.readingOrder.first()) } - /** - * Notification that the user has started a touch gesture. Clients may want to use this - * to disable advancing the seekbar. - * @param seekBar The SeekBar in which the touch gesture began - */ - override fun onStartTrackingTouch(seekBar: SeekBar?) { - // do nothing - isSeekTracking = true - if (DEBUG) Timber.d("start tracking") - } + binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + /** + * Notification that the progress level has changed. Clients can use the fromUser parameter + * to distinguish user-initiated changes from those that occurred programmatically. + * + * @param seekBar The SeekBar whose progress has changed + * @param progress The current progress level. This will be in the range min..max where min + * @param fromUser True if the progress change was initiated by the user. + */ + override fun onProgressChanged( + seekBar: SeekBar?, + progress: Int, + fromUser: Boolean + ) { + if (!fromUser) { + return + } + mediaPlayer.seekTo(progress) + if (DEBUG) Timber.d("progress $progress") + } - /** - * Notification that the user has finished a touch gesture. Clients may want to use this - * to re-enable advancing the seekbar. - * @param seekBar The SeekBar in which the touch gesture began - */ - override fun onStopTrackingTouch(seekBar: SeekBar?) { - // do nothing - isSeekTracking = false - if (DEBUG) Timber.d("stop tracking") - } + /** + * Notification that the user has started a touch gesture. Clients may want to use this + * to disable advancing the seekbar. + * @param seekBar The SeekBar in which the touch gesture began + */ + override fun onStartTrackingTouch(seekBar: SeekBar?) { + // do nothing + isSeekTracking = true + if (DEBUG) Timber.d("start tracking") + } - }) + /** + * Notification that the user has finished a touch gesture. Clients may want to use this + * to re-enable advancing the seekbar. + * @param seekBar The SeekBar in which the touch gesture began + */ + override fun onStopTrackingTouch(seekBar: SeekBar?) { + // do nothing + isSeekTracking = false + if (DEBUG) Timber.d("stop tracking") + } + }) - binding.playPause.setOnClickListener { - mediaPlayer.let { - if (it.isPlaying) { - it.pause() - } else { - if (it.isPaused) { - it.resume() + binding.playPause.setOnClickListener { + mediaPlayer.let { + if (it.isPlaying) { + it.pause() } else { - it.startPlayer() + if (it.isPaused) { + it.resume() + } else { + it.startPlayer() + } + Handler(mainLooper).postDelayed(updateSeekTime, 100) } - Handler(mainLooper).postDelayed(updateSeekTime, 100) + this.updateUI() } - this.updateUI() } - } - binding.playPause.callOnClick() + binding.playPause.callOnClick() - binding.fastForward.setOnClickListener { - if (startTime.toInt() + forwardTime <= finalTime) { - startTime += forwardTime - mediaPlayer.seekTo(startTime) + binding.fastForward.setOnClickListener { + if (startTime.toInt() + forwardTime <= finalTime) { + startTime += forwardTime + mediaPlayer.seekTo(startTime) + } } - } - binding.fastBack.setOnClickListener { - if (startTime.toInt() - backwardTime > 0) { - startTime -= backwardTime - mediaPlayer.seekTo(startTime) + binding.fastBack.setOnClickListener { + if (startTime.toInt() - backwardTime > 0) { + startTime -= backwardTime + mediaPlayer.seekTo(startTime) + } } - } - binding.nextChapter.setOnClickListener { - goForward(false) {} - } + binding.nextChapter.setOnClickListener { + goForward(false) {} + } - binding.prevChapter.setOnClickListener { - goBackward(false) {} + binding.prevChapter.setOnClickListener { + goBackward(false) {} + } } - - } }, 100) } @@ -253,7 +255,6 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit if (currentResource == publication.readingOrder.size - 1) { binding.nextChapter.isEnabled = false binding.nextChapter.alpha = .5f - } else { binding.nextChapter.isEnabled = true binding.nextChapter.alpha = 1.0f @@ -261,7 +262,6 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit if (currentResource == 0) { binding.prevChapter.isEnabled = false binding.prevChapter.alpha = .5f - } else { binding.prevChapter.isEnabled = true binding.prevChapter.alpha = 1.0f @@ -270,7 +270,6 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit val current = publication.readingOrder[currentResource] binding.chapterView.text = current.title - if (mediaPlayer.isPlaying) { binding.playPause.setImageDrawable(ContextCompat.getDrawable(this@R2AudiobookActivity, R.drawable.ic_pause_white_24dp)) } else { @@ -282,13 +281,17 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit binding.seekBar.max = finalTime.toInt() - binding.chapterTime.text = String.format("%d:%d", - TimeUnit.MILLISECONDS.toMinutes(finalTime.toLong()), - TimeUnit.MILLISECONDS.toSeconds(finalTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(finalTime.toLong()))) + binding.chapterTime.text = String.format( + "%d:%d", + TimeUnit.MILLISECONDS.toMinutes(finalTime.toLong()), + TimeUnit.MILLISECONDS.toSeconds(finalTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(finalTime.toLong())) + ) - binding.progressTime.text = String.format("%d:%d", - TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()), - TimeUnit.MILLISECONDS.toSeconds(startTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()))) + binding.progressTime.text = String.format( + "%d:%d", + TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()), + TimeUnit.MILLISECONDS.toSeconds(startTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(startTime.toLong())) + ) binding.seekBar.progress = startTime.toInt() @@ -362,9 +365,11 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit mediaPlayer.let { startTime = it.mediaPlayer.currentPosition.toDouble() } - binding.progressTime.text = String.format("%d:%d", - TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()), - TimeUnit.MILLISECONDS.toSeconds(startTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()))) + binding.progressTime.text = String.format( + "%d:%d", + TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()), + TimeUnit.MILLISECONDS.toSeconds(startTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(startTime.toLong())) + ) binding.seekBar.progress = startTime.toInt() notifyCurrentLocation() @@ -425,9 +430,7 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit } } } - } - } internal fun Link.withBaseUrl(baseUrl: String): Link { diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2MediaPlayer.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2MediaPlayer.kt index 2838054b9e..1347a278b4 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2MediaPlayer.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2MediaPlayer.kt @@ -4,12 +4,11 @@ import android.app.ProgressDialog import android.media.MediaPlayer import android.media.MediaPlayer.OnPreparedListener import android.net.Uri +import java.io.IOException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.readium.r2.shared.publication.Link -import java.io.IOException - class R2MediaPlayer(private var items: List, private var callback: MediaPlayerCallback) : OnPreparedListener { @@ -23,10 +22,10 @@ class R2MediaPlayer(private var items: List, private var callback: MediaPl get() = mediaPlayer.isPlaying val duration: Double - get() = mediaPlayer.duration.toDouble() //if (isPrepared) {mediaPlayer.duration.toDouble()}else {0.0} + get() = mediaPlayer.duration.toDouble() // if (isPrepared) {mediaPlayer.duration.toDouble()}else {0.0} val currentPosition: Double - get() = mediaPlayer.currentPosition.toDouble() //if (isPrepared) {mediaPlayer.currentPosition.toDouble()}else {0.0} + get() = mediaPlayer.currentPosition.toDouble() // if (isPrepared) {mediaPlayer.currentPosition.toDouble()}else {0.0} var isPaused: Boolean var isPrepared: Boolean @@ -44,7 +43,6 @@ class R2MediaPlayer(private var items: List, private var callback: MediaPl toggleProgress(true) } - /** * Called when the media file is ready for playback. * @@ -147,12 +145,9 @@ class R2MediaPlayer(private var items: List, private var callback: MediaPl } toggleProgress(true) } - } interface MediaPlayerCallback { fun onPrepared() fun onComplete(index: Int, currentPosition: Int, duration: Int) } - - diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt index 9b4a683271..f7627aa5f3 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt @@ -21,6 +21,7 @@ import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.asLiveData +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow @@ -39,14 +40,12 @@ import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.ReadingProgression -import kotlin.coroutines.CoroutineContext open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, VisualNavigator, ImageNavigatorFragment.Listener { private val navigatorFragment: ImageNavigatorFragment get() = supportFragmentManager.findFragmentById(R.id.image_navigator) as ImageNavigatorFragment - protected var navigatorDelegate: NavigatorDelegate? = null protected val positions: List get() = navigatorFragment.positions @@ -79,7 +78,6 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis override val readingProgression: ReadingProgression get() = navigatorFragment.readingProgression - /** * Context of this scope. */ @@ -124,11 +122,14 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis resourcePager = navigatorFragment.resourcePager - navigatorFragment.currentLocator.asLiveData().observe(this, Observer { locator -> - locator ?: return@Observer - @Suppress("DEPRECATION") - navigatorDelegate?.locationDidChange(this, locator) - }) + navigatorFragment.currentLocator.asLiveData().observe( + this, + Observer { locator -> + locator ?: return@Observer + @Suppress("DEPRECATION") + navigatorDelegate?.locationDidChange(this, locator) + } + ) // Add support for display cutout. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { @@ -156,16 +157,20 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis if (allowToggleActionBar) { launch { if (supportActionBar!!.isShowing) { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar - or View.SYSTEM_UI_FLAG_IMMERSIVE) + or View.SYSTEM_UI_FLAG_IMMERSIVE + ) } else { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + ) } } } @@ -212,12 +217,14 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis @Suppress("DEPRECATION") if (supportActionBar!!.isShowing && allowToggleActionBar) { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar - or View.SYSTEM_UI_FLAG_IMMERSIVE) + or View.SYSTEM_UI_FLAG_IMMERSIVE + ) } } } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/divina/R2DiViNaActivity.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/divina/R2DiViNaActivity.kt index cd273fae6b..5bc5237657 100755 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/divina/R2DiViNaActivity.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/divina/R2DiViNaActivity.kt @@ -18,6 +18,7 @@ import android.view.View import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity import androidx.webkit.WebViewClientCompat +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -27,8 +28,6 @@ import org.readium.r2.navigator.VisualNavigator import org.readium.r2.navigator.databinding.ActivityR2DivinaBinding import org.readium.r2.shared.extensions.getPublication import org.readium.r2.shared.publication.Publication -import kotlin.coroutines.CoroutineContext - open class R2DiViNaActivity : AppCompatActivity(), CoroutineScope, IR2Activity, VisualNavigator.Listener { @@ -57,7 +56,7 @@ open class R2DiViNaActivity : AppCompatActivity(), CoroutineScope, IR2Activity, preferences = getSharedPreferences("org.readium.r2.settings", Context.MODE_PRIVATE) divinaWebView = binding.divinaWebView - //divinaWebView.listener = this + // divinaWebView.listener = this publication = intent.getPublication(this) publicationPath = intent.getStringExtra("publicationPath") ?: throw Exception("publicationPath required") @@ -79,27 +78,30 @@ open class R2DiViNaActivity : AppCompatActivity(), CoroutineScope, IR2Activity, // divinaWebView.evaluateJavascript("window.androidObj.toggleMenu = function() { Android.toggleMenu() };", null) // Now launch the DiViNa player for the folderPath = publicationPath - divinaWebView.evaluateJavascript("if (player) { player.openDiViNaFromPath('${publicationPath}'); };", null) + divinaWebView.evaluateJavascript("if (player) { player.openDiViNaFromPath('$publicationPath'); };", null) } } divinaWebView.loadUrl("file:///android_asset/readium/divina/divinaPlayer.html") - } @Suppress("DEPRECATION") override fun toggleActionBar() { launch { if (supportActionBar!!.isShowing) { - divinaWebView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + divinaWebView.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar - or View.SYSTEM_UI_FLAG_IMMERSIVE) + or View.SYSTEM_UI_FLAG_IMMERSIVE + ) } else { - divinaWebView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + divinaWebView.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + ) } } } @@ -114,4 +116,3 @@ open class R2DiViNaActivity : AppCompatActivity(), CoroutineScope, IR2Activity, divinaWebView.evaluateJavascript("if (player) { player.destroy(); };", null) } } - diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFactory.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFactory.kt index 3905333cca..7335fc5d52 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFactory.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFactory.kt @@ -44,18 +44,18 @@ class EpubNavigatorFactory( paginationListener: EpubNavigatorFragment.PaginationListener? = null, configuration: EpubNavigatorFragment.Configuration = EpubNavigatorFragment.Configuration(), ) = org.readium.r2.navigator.util.createFragmentFactory { - EpubNavigatorFragment( - publication = publication, - baseUrl = null, - initialLocator = initialLocator, - initialPreferences = initialPreferences, - listener = listener, - paginationListener = paginationListener, - epubLayout = layout, - defaults = this.configuration.defaults, - configuration = configuration - ) - } + EpubNavigatorFragment( + publication = publication, + baseUrl = null, + initialLocator = initialLocator, + initialPreferences = initialPreferences, + listener = listener, + paginationListener = paginationListener, + epubLayout = layout, + defaults = this.configuration.defaults, + configuration = configuration + ) + } fun createPreferencesEditor( currentPreferences: EpubPreferences, diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt index dc40596e93..f92c7df883 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt @@ -30,6 +30,8 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.whenStarted import androidx.viewpager.widget.ViewPager +import kotlin.math.ceil +import kotlin.reflect.KClass import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -42,7 +44,10 @@ import org.json.JSONObject import org.readium.r2.navigator.* import org.readium.r2.navigator.databinding.ActivityR2ViewpagerBinding import org.readium.r2.navigator.epub.EpubNavigatorViewModel.RunScriptCommand -import org.readium.r2.navigator.epub.css.* +import org.readium.r2.navigator.epub.css.FontFamilyDeclaration +import org.readium.r2.navigator.epub.css.MutableFontFamilyDeclaration +import org.readium.r2.navigator.epub.css.RsProperties +import org.readium.r2.navigator.epub.css.buildFontFamilyDeclaration import org.readium.r2.navigator.extensions.optRectF import org.readium.r2.navigator.extensions.positionsByResource import org.readium.r2.navigator.html.HtmlDecorationTemplates @@ -67,8 +72,6 @@ import org.readium.r2.shared.publication.services.isRestricted import org.readium.r2.shared.publication.services.positionsByReadingOrder import org.readium.r2.shared.util.launchWebBrowser import org.readium.r2.shared.util.mediatype.MediaType -import kotlin.math.ceil -import kotlin.reflect.KClass /** * Factory for a [JavascriptInterface] which will be injected in the web views. @@ -124,7 +127,7 @@ class EpubNavigatorFragment internal constructor( * Font declarations to inject through Readium CSS. */ @ExperimentalReadiumApi - internal val fontFamilyDeclarations: MutableList = mutableListOf(), + internal val fontFamilyDeclarations: MutableList = mutableListOf(), /** * Readium CSS reading system settings. @@ -167,7 +170,10 @@ class EpubNavigatorFragment internal constructor( * Adds a declaration for [fontFamily] using [builderAction]. */ @ExperimentalReadiumApi - fun addFontFamilyDeclaration(fontFamily: FontFamily, builderAction: (MutableFontFamilyDeclaration).() -> Unit) { + fun addFontFamilyDeclaration( + fontFamily: FontFamily, + builderAction: (MutableFontFamilyDeclaration).() -> Unit + ) { val declaration = buildFontFamilyDeclaration(fontFamily.name, builderAction) fontFamilyDeclarations.add(declaration) } @@ -178,10 +184,10 @@ class EpubNavigatorFragment internal constructor( fun onPageLoaded() {} } - interface Listener: VisualNavigator.Listener + interface Listener : VisualNavigator.Listener init { - require(!publication.isRestricted) { "The provided publication is restricted. Check that any DRM was properly unlocked using a Content Protection."} + require(!publication.isRestricted) { "The provided publication is restricted. Check that any DRM was properly unlocked using a Content Protection." } } override val presentation: StateFlow get() = viewModel.presentation @@ -239,7 +245,11 @@ class EpubNavigatorFragment internal constructor( private var _binding: ActivityR2ViewpagerBinding? = null private val binding get() = _binding!! - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { currentActivity = requireActivity() _binding = ActivityR2ViewpagerBinding.inflate(inflater, container, false) val view = binding.root @@ -339,7 +349,6 @@ class EpubNavigatorFragment internal constructor( notifyCurrentLocation() } - }) return view @@ -554,7 +563,7 @@ class EpubNavigatorFragment internal constructor( val webView = currentReflowablePageFragment?.webView ?: return null val json = webView.runJavaScriptSuspend("readium.getCurrentSelection();") - .takeIf { it != "null"} + .takeIf { it != "null" } ?.let { tryOrLog { JSONObject(it) } } ?: return null @@ -635,12 +644,14 @@ class EpubNavigatorFragment internal constructor( override fun onScroll() { val activity = r2Activity ?: return if (activity.supportActionBar?.isShowing == true && activity.allowToggleActionBar) { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar - or View.SYSTEM_UI_FLAG_IMMERSIVE) + or View.SYSTEM_UI_FLAG_IMMERSIVE + ) } } @@ -665,7 +676,12 @@ class EpubNavigatorFragment internal constructor( offset = event.offset ) ?: false - override fun onDecorationActivated(id: DecorationId, group: String, rect: RectF, point: PointF): Boolean = + override fun onDecorationActivated( + id: DecorationId, + group: String, + rect: RectF, + point: PointF + ): Boolean = viewModel.onDecorationActivated( id = id, group = group, @@ -788,7 +804,7 @@ class EpubNavigatorFragment internal constructor( private val r2PagerAdapter: R2PagerAdapter? get() = if (::resourcePager.isInitialized) resourcePager.adapter as? R2PagerAdapter - else null + else null private val currentReflowablePageFragment: R2EpubPageFragment? get() = currentFragment as? R2EpubPageFragment @@ -819,8 +835,9 @@ class EpubNavigatorFragment internal constructor( get() = viewModel.readingProgression override val currentLocator: StateFlow get() = _currentLocator - private val _currentLocator = MutableStateFlow(initialLocator - ?: requireNotNull(publication.locatorFromLink(publication.readingOrder.first())) + private val _currentLocator = MutableStateFlow( + initialLocator + ?: requireNotNull(publication.locatorFromLink(publication.readingOrder.first())) ) /** @@ -852,7 +869,7 @@ class EpubNavigatorFragment internal constructor( var result: MutableMap = mutableMapOf() for (link in linkList) { - val title = link.title?: "" + val title = link.title ?: "" if (title.isNotEmpty()) { result[link.href] = title @@ -960,5 +977,4 @@ class EpubNavigatorFragment internal constructor( fun assetUrl(path: String): String = WebViewServer.assetUrl(path) } - } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorViewModel.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorViewModel.kt index b55a83dd15..22db459060 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorViewModel.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorViewModel.kt @@ -16,12 +16,12 @@ import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import kotlin.reflect.KClass import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import org.json.JSONObject import org.readium.r2.navigator.* -import org.readium.r2.navigator.epub.css.FontFamilyDeclaration import org.readium.r2.navigator.epub.css.ReadiumCss import org.readium.r2.navigator.epub.extensions.javascriptForGroup import org.readium.r2.navigator.html.HtmlDecorationTemplates @@ -34,10 +34,9 @@ import org.readium.r2.shared.extensions.addPrefix import org.readium.r2.shared.extensions.mapStateIn import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Publication +import org.readium.r2.shared.publication.ReadingProgression as PublicationReadingProgression import org.readium.r2.shared.publication.epub.EpubLayout import org.readium.r2.shared.util.Href -import kotlin.reflect.KClass -import org.readium.r2.shared.publication.ReadingProgression as PublicationReadingProgression internal enum class DualPage { AUTO, OFF, ON @@ -139,12 +138,14 @@ internal class EpubNavigatorViewModel( properties += css.userProperties.toCssProperties() } if (properties.isNotEmpty()) { - _events.send(Event.RunScript( - RunScriptCommand( - script = "readium.setCSSProperties(${JSONObject(properties.toMap())});", - scope = RunScriptCommand.Scope.LoadedResources + _events.send( + Event.RunScript( + RunScriptCommand( + script = "readium.setCSSProperties(${JSONObject(properties.toMap())});", + scope = RunScriptCommand.Scope.LoadedResources + ) ) - )) + ) } previousCss = css @@ -230,7 +231,7 @@ internal class EpubNavigatorViewModel( css.update { it.update(newSettings) } val needsInvalidation: Boolean = ( - oldSettings.readingProgression != newSettings.readingProgression|| + oldSettings.readingProgression != newSettings.readingProgression || oldSettings.language != newSettings.language || oldSettings.verticalText != newSettings.verticalText || oldSettings.spread != newSettings.spread @@ -290,7 +291,6 @@ internal class EpubNavigatorViewModel( settings.mapStateIn(viewModelScope) { if (layout == EpubLayout.REFLOWABLE) it.scroll else false } - } // Selection @@ -317,13 +317,15 @@ internal class EpubNavigatorViewModel( val cmds = mutableListOf() if (target.isEmpty()) { - cmds.add(RunScriptCommand( - // The updates command are using `requestAnimationFrame()`, so we need it for - // `clear()` as well otherwise we might recreate a highlight after it has been - // cleared. - "requestAnimationFrame(function () { readium.getDecorations('$group').clear(); });", - scope = RunScriptCommand.Scope.LoadedResources - )) + cmds.add( + RunScriptCommand( + // The updates command are using `requestAnimationFrame()`, so we need it for + // `clear()` as well otherwise we might recreate a highlight after it has been + // cleared. + "requestAnimationFrame(function () { readium.getDecorations('$group').clear(); });", + scope = RunScriptCommand.Scope.LoadedResources + ) + ) } else { for ((href, changes) in source.changesByHref(target)) { val script = changes.javascriptForGroup(group, decorationTemplates) ?: continue @@ -371,17 +373,21 @@ internal class EpubNavigatorViewModel( companion object { fun createFactory( - application: Application, publication: Publication, baseUrl: String?, + application: Application, + publication: Publication, + baseUrl: String?, layout: EpubLayout, - defaults: EpubDefaults, config: EpubNavigatorFragment.Configuration, + defaults: EpubDefaults, + config: EpubNavigatorFragment.Configuration, initialPreferences: EpubPreferences ) = createViewModelFactory { - EpubNavigatorViewModel(application, publication, config, initialPreferences, layout, + EpubNavigatorViewModel( + application, publication, config, initialPreferences, layout, defaults = defaults, baseUrl = baseUrl, server = if (baseUrl != null) null - else WebViewServer(application, publication, servedAssets = config.servedAssets) + else WebViewServer(application, publication, servedAssets = config.servedAssets) ) } } -} \ No newline at end of file +} diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferences.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferences.kt index 666e1dcaec..322b100792 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferences.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferences.kt @@ -73,7 +73,7 @@ data class EpubPreferences( val typeScale: Double? = null, val verticalText: Boolean? = null, val wordSpacing: Double? = null -): Configurable.Preferences { +) : Configurable.Preferences { init { require(fontSize == null || fontSize >= 0) @@ -114,7 +114,6 @@ data class EpubPreferences( ) } - /** * Loads the preferences from the legacy EPUB settings stored in the [SharedPreferences] with * given [sharedPreferencesName]. @@ -144,7 +143,6 @@ fun EpubPreferences.Companion.fromLegacyEpubSettings( ?.takeUnless { it == "Original" } ?.let { FontFamily(it) } - val theme = sp .takeIf { sp.contains("appearance") } ?.getInt("appearance", 0) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferencesEditor.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferencesEditor.kt index 068d7e251b..3fea176f2f 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferencesEditor.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubPreferencesEditor.kt @@ -28,7 +28,7 @@ class EpubPreferencesEditor internal constructor( val layout: EpubLayout, defaults: EpubDefaults, configuration: Configuration -): PreferencesEditor { +) : PreferencesEditor { /** * Configuration for [EpubPreferencesEditor]. @@ -46,24 +46,24 @@ class EpubPreferencesEditor internal constructor( val pageMarginsRange: ClosedRange = 0.5..4.0, val pageMarginsProgression: ProgressionStrategy = DoubleIncrement(0.3) ) - + private data class State( val preferences: EpubPreferences, val settings: EpubSettings, val layout: Layout ) - + private val settingsResolver: EpubSettingsResolver = EpubSettingsResolver(publicationMetadata, defaults) private var state: State = - initialPreferences.toState() + initialPreferences.toState() override val preferences: EpubPreferences get() = state.preferences override fun clear() { - updateValues { EpubPreferences() } + updateValues { EpubPreferences() } } val backgroundColor: Preference = @@ -73,7 +73,7 @@ class EpubPreferencesEditor internal constructor( getIsEffective = { layout == EpubLayout.REFLOWABLE }, updateValue = { value -> updateValues { it.copy(backgroundColor = value) } }, ) - + val columnCount: EnumPreference = EnumPreferenceDelegate( getValue = { preferences.columnCount }, @@ -108,7 +108,7 @@ class EpubPreferencesEditor internal constructor( getValue = { preferences.hyphens }, getEffectiveValue = { state.settings.hyphens }, getIsEffective = { isHyphensEffective() }, - updateValue = { value -> updateValues { it.copy(hyphens = value) }}, + updateValue = { value -> updateValues { it.copy(hyphens = value) } }, ) val imageFilter: EnumPreference = @@ -196,7 +196,7 @@ class EpubPreferencesEditor internal constructor( getValue = { preferences.publisherStyles }, getEffectiveValue = { state.settings.publisherStyles }, getIsEffective = { layout == EpubLayout.REFLOWABLE }, - updateValue = { value -> updateValues { it.copy(publisherStyles = value) }}, + updateValue = { value -> updateValues { it.copy(publisherStyles = value) } }, ) val readingProgression: EnumPreference = @@ -313,7 +313,6 @@ class EpubPreferencesEditor internal constructor( state.layout.stylesheets == Layout.Stylesheets.Default && !state.settings.publisherStyles - private fun isLetterSpacing() = layout == EpubLayout.REFLOWABLE && state.layout.stylesheets == Layout.Stylesheets.Default && !state.settings.publisherStyles diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettings.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettings.kt index 022fae32dd..7dfb32c2b9 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettings.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettings.kt @@ -6,16 +6,20 @@ package org.readium.r2.navigator.epub -import org.readium.r2.navigator.epub.css.* +import org.readium.r2.navigator.epub.css.Appearance +import org.readium.r2.navigator.epub.css.ColCount +import org.readium.r2.navigator.epub.css.Color as CssColor +import org.readium.r2.navigator.epub.css.Hyphens import org.readium.r2.navigator.epub.css.Layout +import org.readium.r2.navigator.epub.css.Length +import org.readium.r2.navigator.epub.css.Ligatures import org.readium.r2.navigator.epub.css.ReadiumCss import org.readium.r2.navigator.epub.css.TextAlign as CssTextAlign -import org.readium.r2.navigator.epub.css.Color as CssColor +import org.readium.r2.navigator.epub.css.View import org.readium.r2.navigator.preferences.* import org.readium.r2.navigator.preferences.Color import org.readium.r2.navigator.preferences.TextAlign import org.readium.r2.shared.ExperimentalReadiumApi -import org.readium.r2.shared.extensions.addPrefix import org.readium.r2.shared.util.Either import org.readium.r2.shared.util.Language @@ -58,13 +62,13 @@ internal fun ReadiumCss.update(settings: EpubSettings): ReadiumCss { fun FontFamily.toCss(): List = buildList { add(name) val alternateChain = alternate?.toCss() - alternateChain?.let { addAll(it) } + alternateChain?.let { addAll(it) } } fun Color.toCss(): CssColor = CssColor.Int(int) - return with (settings) { + return with(settings) { copy( layout = Layout.from(settings), userProperties = userProperties.copy( diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettingsResolver.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettingsResolver.kt index 5d212f97b6..93ad66bf05 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettingsResolver.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubSettingsResolver.kt @@ -48,7 +48,7 @@ internal class EpubSettingsResolver( lineHeight = preferences.lineHeight ?: defaults.lineHeight ?: 1.2, pageMargins = preferences.pageMargins ?: defaults.pageMargins ?: 1.0, paragraphIndent = preferences.paragraphIndent ?: defaults.paragraphIndent ?: 0.0, - paragraphSpacing = preferences.paragraphSpacing ?: defaults.paragraphSpacing ?: 0.0, + paragraphSpacing = preferences.paragraphSpacing ?: defaults.paragraphSpacing ?: 0.0, publisherStyles = preferences.publisherStyles ?: defaults.publisherStyles ?: true, scroll = preferences.scroll ?: defaults.scroll ?: false, textAlign = preferences.textAlign ?: defaults.textAlign ?: TextAlign.START, @@ -106,8 +106,8 @@ internal class EpubSettingsResolver( language: Language?, readingProgression: ReadingProgression ) = when { - verticalPreference != null -> verticalPreference - language != null -> language.isCjk && readingProgression == ReadingProgression.RTL - else -> false - } + verticalPreference != null -> verticalPreference + language != null -> language.isCjk && readingProgression == ReadingProgression.RTL + else -> false + } } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/HtmlInjector.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/HtmlInjector.kt index 06bec693ac..254ceb1930 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/HtmlInjector.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/HtmlInjector.kt @@ -41,14 +41,16 @@ internal fun Resource.injectHtml(publication: Publication, css: ReadiumCss, base // Disable the text selection if the publication is protected. // FIXME: This is a hack until proper LCP copy is implemented, see https://github.com/readium/kotlin-toolkit/issues/221 if (publication.isProtected) { - injectables.add(""" + injectables.add( + """ - """) + """ + ) } val headEndIndex = content.indexOf("", 0, true) diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Highlightable.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Highlightable.kt index ef969a9572..09f1c3ed42 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Highlightable.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Highlightable.kt @@ -39,4 +39,4 @@ data class Highlight( enum class Style { highlight, underline, strikethrough -} \ No newline at end of file +} diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Selectable.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Selectable.kt index 199b0b7c0b..b91ee3454c 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Selectable.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/IR2Selectable.kt @@ -10,4 +10,4 @@ import org.readium.r2.shared.publication.Locator interface IR2Selectable { fun currentSelection(callback: (Locator?) -> Unit) -} \ No newline at end of file +} diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt index d4a42a646f..acb79f5500 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt @@ -19,6 +19,7 @@ import android.view.ActionMode import android.view.View import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow @@ -35,9 +36,8 @@ import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.ReadingProgression -import kotlin.coroutines.CoroutineContext -open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2Highlightable, IR2TTS, CoroutineScope, VisualNavigator, EpubNavigatorFragment.Listener { +open class R2EpubActivity : AppCompatActivity(), IR2Activity, IR2Selectable, IR2Highlightable, IR2TTS, CoroutineScope, VisualNavigator, EpubNavigatorFragment.Listener { /** * Context of this scope. @@ -66,7 +66,6 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H private val currentFragment: R2EpubPageFragment? get() = adapter.mFragments.get(adapter.getItemId(resourcePager.currentItem)) as? R2EpubPageFragment - // For backward compatibility, we expose these properties only through the `R2EpubActivity`. val positions: List get() = navigatorFragment().positions val currentPagerPosition: Int get() = navigatorFragment().currentPagerPosition @@ -137,16 +136,20 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H override fun toggleActionBar() { if (allowToggleActionBar) { if (supportActionBar!!.isShowing) { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar - or View.SYSTEM_UI_FLAG_IMMERSIVE) + or View.SYSTEM_UI_FLAG_IMMERSIVE + ) } else { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + ) } } } @@ -163,12 +166,14 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H navigatorFragment().go(locator, animated, completion) if (allowToggleActionBar && supportActionBar!!.isShowing) { - resourcePager.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + resourcePager.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar - or View.SYSTEM_UI_FLAG_IMMERSIVE) + or View.SYSTEM_UI_FLAG_IMMERSIVE + ) } return true @@ -225,7 +230,7 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H } override fun showHighlights(highlights: Array) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } override fun hideHighlightWithID(id: String) { @@ -234,7 +239,7 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H } override fun hideAllHighlights() { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } override fun rectangleForHighlightWithID(id: String, callback: (Rect?) -> Unit) { @@ -259,11 +264,11 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H } override fun rectangleForHighlightAnnotationMarkWithID(id: String): Rect? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } override fun registerHighlightAnnotationMarkStyle(name: String, css: String) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } override fun highlightActivated(id: String) { @@ -284,12 +289,12 @@ open class R2EpubActivity: AppCompatActivity(), IR2Activity, IR2Selectable, IR2H val json = JSONObject(it) val id = json.getString("id") callback( - Highlight( - id, - locator!!, - color, - Style.highlight - ) + Highlight( + id, + locator!!, + color, + Style.highlight + ) ) } } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/WebViewServer.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/WebViewServer.kt index c8537ae6b9..f31489b0c6 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/WebViewServer.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/WebViewServer.kt @@ -94,7 +94,6 @@ internal class WebViewServer( if (range == null) { return WebResourceResponse(link.type, null, 200, "OK", headers, ResourceInputStream(resource)) - } else { // Byte range request val stream = ResourceInputStream(resource) val length = stream.available() @@ -128,4 +127,4 @@ internal class WebViewServer( .setDomain("readium") .addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(application)) .build() -} \ No newline at end of file +} diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/FontFamilyDeclaration.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/FontFamilyDeclaration.kt index e699c3b464..609ca0fd52 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/FontFamilyDeclaration.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/FontFamilyDeclaration.kt @@ -12,7 +12,10 @@ import org.readium.r2.shared.ExperimentalReadiumApi * Build a declaration for [fontFamily] using [builderAction]. */ @ExperimentalReadiumApi -fun buildFontFamilyDeclaration(fontFamily: String, builderAction: (MutableFontFamilyDeclaration).() -> Unit) = +fun buildFontFamilyDeclaration( + fontFamily: String, + builderAction: (MutableFontFamilyDeclaration).() -> Unit +) = MutableFontFamilyDeclaration(fontFamily).apply(builderAction).toFontFamilyDeclaration() /** @@ -28,8 +31,11 @@ data class FontFamilyDeclaration internal constructor( * Build a font face declaration for [fontFamily]. */ @ExperimentalReadiumApi -internal fun buildFontFaceDeclaration(fontFamily: String, builderAction: (MutableFontFaceDeclaration).() -> Unit) = - MutableFontFaceDeclaration(fontFamily).apply(builderAction).toFontFaceDeclaration() +internal fun buildFontFaceDeclaration( + fontFamily: String, + builderAction: (MutableFontFaceDeclaration).() -> Unit +) = + MutableFontFaceDeclaration(fontFamily).apply(builderAction).toFontFaceDeclaration() /** * An immutable font face declaration. diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/Properties.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/Properties.kt index d1f11d7447..32812ffe2c 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/Properties.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/Properties.kt @@ -7,16 +7,16 @@ package org.readium.r2.navigator.epub.css import androidx.annotation.ColorInt -import org.readium.r2.shared.ExperimentalReadiumApi -import org.readium.r2.shared.util.Either import java.text.NumberFormat import java.util.* +import org.readium.r2.shared.ExperimentalReadiumApi +import org.readium.r2.shared.util.Either /** * Holds a set of Readium CSS properties applied together. */ @ExperimentalReadiumApi -interface Properties: Cssable { +interface Properties : Cssable { fun toCssProperties(): Map override fun toCss(): String? { @@ -365,7 +365,7 @@ enum class Appearance(private val css: String?) : Cssable { @ExperimentalReadiumApi interface Color : Cssable { - data class Rgb(val red: kotlin.Int, val green: kotlin.Int, val blue: kotlin.Int): Color { + data class Rgb(val red: kotlin.Int, val green: kotlin.Int, val blue: kotlin.Int) : Color { init { require(red in 0..255) require(green in 0..255) @@ -376,7 +376,7 @@ interface Color : Cssable { } @JvmInline - value class Hex(val color: String): Color { + value class Hex(val color: String) : Color { init { require(Regex("^#(?:[0-9a-fA-F]{3}){1,2}$").matches(color)) } @@ -385,7 +385,7 @@ interface Color : Cssable { } @JvmInline - value class Int(@ColorInt val color: kotlin.Int): Color { + value class Int(@ColorInt val color: kotlin.Int) : Color { override fun toCss(): String = String.format("#%06X", 0xFFFFFF and color) } diff --git a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/ReadiumCss.kt b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/ReadiumCss.kt index 3f42dc3893..3f02ca8f33 100644 --- a/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/ReadiumCss.kt +++ b/readium/navigator/src/main/java/org/readium/r2/navigator/epub/css/ReadiumCss.kt @@ -50,41 +50,51 @@ internal data class ReadiumCss( val stylesheetsFolder = assetsBaseHref + "/readium/readium-css/" + (layout.stylesheets.folder?.plus("/") ?: "") val headBeforeIndex = content.indexForOpeningTag("head") - content.insert(headBeforeIndex, "\n" + buildList { - add(stylesheetLink(stylesheetsFolder + "ReadiumCSS-before.css")) - - // Fix Readium CSS issue with the positioning of