diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt index c2190a20b6..576bf5dbe4 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/mimetype/MimeTypeTest.kt @@ -76,6 +76,7 @@ class MimeTypeTest : BaseActivityTest() { } val zimFileReader = ZimFileReader( zimFile, + null, Archive(zimFile.canonicalPath), NightModeConfig(SharedPreferenceUtil(context), context) ) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialog.kt index e280061a1d..5cd95a7f7e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/AddNoteDialog.kt @@ -117,13 +117,13 @@ class AddNoteDialog : DialogFragment() { .inject(this) // Returns name of the form ".../Kiwix/granbluefantasy_en_all_all_nopic_2018-10.zim" - zimFileName = zimReaderContainer.zimCanonicalPath + zimFileName = zimReaderContainer.zimCanonicalPath ?: zimReaderContainer.name if (zimFileName != null) { // No zim file currently opened zimFileTitle = zimReaderContainer.zimFileTitle zimId = zimReaderContainer.id.orEmpty() if (arguments != null) { - articleTitle = arguments?.getString(NOTES_TITLE) + articleTitle = arguments?.getString(NOTES_TITLE)?.substringAfter(": ") zimFileUrl = arguments?.getString(ARTICLE_URL).orEmpty() } else { val webView = (activity as WebViewProvider?)?.getCurrentWebView() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index a579544d07..18e7236be6 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -26,6 +26,7 @@ import android.content.Intent import android.content.ServiceConnection import android.content.SharedPreferences import android.content.pm.PackageManager +import android.content.res.AssetFileDescriptor import android.content.res.Configuration import android.graphics.Canvas import android.graphics.Rect @@ -1386,13 +1387,20 @@ abstract class CoreReaderFragment : externalLinkOpener?.openExternalUrl(intent) } - protected fun openZimFile(file: File, isCustomApp: Boolean = false) { + protected fun openZimFile( + file: File?, + isCustomApp: Boolean = false, + assetFileDescriptor: AssetFileDescriptor? = null + ) { if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || isCustomApp) { - if (file.isFileExist()) { - openAndSetInContainer(file) + if (file?.isFileExist() == true) { + openAndSetInContainer(file = file) + updateTitle() + } else if (assetFileDescriptor != null) { + openAndSetInContainer(assetFileDescriptor = assetFileDescriptor) updateTitle() } else { - Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + file.absolutePath) + Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + file?.absolutePath) requireActivity().toast(R.string.error_file_not_found, Toast.LENGTH_LONG) } } else { @@ -1419,16 +1427,24 @@ abstract class CoreReaderFragment : ) } - private fun openAndSetInContainer(file: File) { + private fun openAndSetInContainer( + file: File? = null, + assetFileDescriptor: AssetFileDescriptor? = null + ) { try { - if (isNotPreviouslyOpenZim(file.canonicalPath)) { + if (isNotPreviouslyOpenZim(file?.canonicalPath)) { webViewList.clear() } } catch (e: IOException) { e.printStackTrace() } zimReaderContainer?.let { zimReaderContainer -> - zimReaderContainer.setZimFile(file) + if (assetFileDescriptor != null) { + zimReaderContainer.setZimFileDescriptor(assetFileDescriptor) + } else { + zimReaderContainer.setZimFile(file) + } + val zimFileReader = zimReaderContainer.zimFileReader zimFileReader?.let { zimFileReader -> // uninitialized the service worker to fix https://github.com/kiwix/kiwix-android/issues/2561 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt index 4f6a440cc9..6acee4c889 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt @@ -24,6 +24,7 @@ import org.kiwix.kiwixmobile.core.di.ActivityScope import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem +import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import javax.inject.Inject private const val TAG = "MainPresenter" @@ -33,6 +34,7 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour private var saveHistoryDisposable: Disposable? = null private var saveBookmarkDisposable: Disposable? = null private var saveNoteDisposable: Disposable? = null + private var saveBookDisposable: Disposable? = null private var deleteNoteDisposable: Disposable? = null fun saveHistory(history: HistoryItem) { @@ -61,10 +63,16 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour .subscribe({}, { e -> Log.e(TAG, "Unable to delete note", e) }) } + fun saveBook(book: BookOnDisk) { + saveBookDisposable = dataSource.saveBook(book) + .subscribe({}, { e -> Log.e(TAG, "Unable to save book", e) }) + } + fun dispose() { saveHistoryDisposable?.dispose() saveBookmarkDisposable?.dispose() saveNoteDisposable?.dispose() deleteNoteDisposable?.dispose() + saveBookDisposable?.dispose() } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt index 0f5401103b..552028020d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/BookmarkItem.kt @@ -51,7 +51,7 @@ data class BookmarkItem( ) : this( zimId = zimFileReader.id, zimName = zimFileReader.name, - zimFilePath = zimFileReader.zimFile.canonicalPath, + zimFilePath = zimFileReader.zimFile?.canonicalPath, bookmarkUrl = url, title = title, favicon = zimFileReader.favicon diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt index eee4fca52b..8556e4086e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/adapter/HistoryListItem.kt @@ -48,7 +48,7 @@ sealed class HistoryListItem : PageRelated { ) : this( zimId = zimFileReader.id, zimName = zimFileReader.name, - zimFilePath = zimFileReader.zimFile.canonicalPath, + zimFilePath = zimFileReader.zimFile?.canonicalPath ?: "", favicon = zimFileReader.favicon, historyUrl = url, title = title, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt index 4f40d24cd8..1741fab2f9 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/adapter/NoteListItem.kt @@ -35,7 +35,7 @@ data class NoteListItem( ) : this( zimId = zimFileReader.id, title = title, - zimFilePath = zimFileReader.zimFile.canonicalPath, + zimFilePath = zimFileReader.zimFile?.canonicalPath, zimUrl = url, favicon = zimFileReader.favicon, noteFilePath = noteFilePath diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt index 77873906d4..8adaa9b4ae 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt @@ -45,8 +45,13 @@ data class ShowOpenNoteDialog( { effects.offer(OpenPage(page, zimReaderContainer)) }, { val item = page as NoteListItem - val file = File(item.zimFilePath.orEmpty()) - zimReaderContainer.setZimFile(file) + // Check if zimFilePath is not null, and then set it in zimReaderContainer. + // For custom apps, we are currently using fileDescriptor, and they only have a single file in them, + // which is already set in zimReaderContainer, so there's no need to set it again. + item.zimFilePath?.let { + val file = File(it) + zimReaderContainer.setZimFile(file) + } effects.offer(OpenNote(item.noteFilePath, item.zimUrl, item.title)) } ) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt index b8553d56e4..5a85bf35cf 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt @@ -52,13 +52,15 @@ import javax.inject.Inject private const val TAG = "ZimFileReader" class ZimFileReader constructor( - val zimFile: File, + val zimFile: File?, + val assetFileDescriptor: AssetFileDescriptor? = null, private val jniKiwixReader: Archive, private val nightModeConfig: NightModeConfig, private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader) ) { interface Factory { fun create(file: File): ZimFileReader? + fun create(assetFileDescriptor: AssetFileDescriptor): ZimFileReader? class Impl @Inject constructor(private val nightModeConfig: NightModeConfig) : Factory { @@ -76,6 +78,26 @@ class ZimFileReader constructor( } catch (ignore: Exception) { // for handing the error, if any zim file is corrupted null } + + override fun create(assetFileDescriptor: AssetFileDescriptor): ZimFileReader? = + try { + ZimFileReader( + null, + assetFileDescriptor, + nightModeConfig = nightModeConfig, + jniKiwixReader = Archive( + assetFileDescriptor.parcelFileDescriptor.dup().fileDescriptor, + assetFileDescriptor.startOffset, + assetFileDescriptor.length + ) + ).also { + Log.e(TAG, "create: with fileDescriptor") + } + } catch (ignore: JNIKiwixException) { + null + } catch (ignore: Exception) { // for handing the error, if any zim file is corrupted + null + } } } @@ -289,6 +311,7 @@ class ZimFileReader constructor( fun dispose() { jniKiwixReader.dispose() searcher.dispose() + assetFileDescriptor?.parcelFileDescriptor?.detachFd() } @Suppress("TooGenericExceptionCaught") diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt index 5c0218228a..9c7b769028 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt @@ -17,6 +17,7 @@ */ package org.kiwix.kiwixmobile.core.reader +import android.content.res.AssetFileDescriptor import android.webkit.WebResourceResponse import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory @@ -42,6 +43,13 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F else null } + fun setZimFileDescriptor(assetFileDescriptor: AssetFileDescriptor) { + zimFileReader = + if (assetFileDescriptor.parcelFileDescriptor.dup().fileDescriptor.valid()) + zimFileReaderFactory.create(assetFileDescriptor) + else null + } + fun getPageUrlFromTitle(title: String) = zimFileReader?.getPageUrlFrom(title) fun getRandomArticleUrl() = zimFileReader?.getRandomArticleUrl() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt index 67642e208a..942ba5a100 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt @@ -29,6 +29,7 @@ import android.provider.DocumentsContract import android.util.Log import android.webkit.URLUtil import android.widget.Toast +import androidx.core.content.ContextCompat import androidx.documentfile.provider.DocumentFile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -402,4 +403,8 @@ object FileUtils { null } } + + @JvmStatic + fun getDemoFilePathForCustomApp(context: Context) = + "${ContextCompat.getExternalFilesDirs(context, null)[0]}/demo.zim" } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/KiwixServer.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/KiwixServer.kt index b2071029b4..5d2fef13ec 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/KiwixServer.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/KiwixServer.kt @@ -18,7 +18,10 @@ package org.kiwix.kiwixmobile.core.webserver +import android.content.Context import android.util.Log +import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer +import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp import org.kiwix.libkiwix.Book import org.kiwix.libkiwix.JNIKiwixException import org.kiwix.libkiwix.Library @@ -36,13 +39,32 @@ class KiwixServer @Inject constructor( private val jniKiwixServer: Server ) { - class Factory @Inject constructor() { + class Factory @Inject constructor( + private val context: Context, + private val zimReaderContainer: ZimReaderContainer + ) { + @Suppress("NestedBlockDepth") fun createKiwixServer(selectedBooksPath: ArrayList): KiwixServer { val kiwixLibrary = Library() selectedBooksPath.forEach { path -> try { val book = Book().apply { - update(Archive(path)) + // Determine whether to create an Archive from an asset or a file path + val archive = if (path == getDemoFilePathForCustomApp(context)) { + // For custom apps using a demo file, create an Archive with FileDescriptor + val assetFileDescriptor = zimReaderContainer.zimFileReader?.assetFileDescriptor + val startOffset = assetFileDescriptor?.startOffset ?: 0L + val size = assetFileDescriptor?.length ?: 0L + Archive( + assetFileDescriptor?.parcelFileDescriptor?.dup()?.fileDescriptor, + startOffset, + size + ) + } else { + // For regular files, create an Archive from the file path + Archive(path) + } + update(archive) } kiwixLibrary.addBook(book) } catch (e: JNIKiwixException) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt index d9f0052ccd..84f57414ed 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt @@ -49,10 +49,12 @@ import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.databinding.ActivityZimHostBinding import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.cachedComponent import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission +import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.navigateToAppSettings import org.kiwix.kiwixmobile.core.reader.ZimFileReader +import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.utils.ConnectivityReporter import org.kiwix.kiwixmobile.core.utils.REQUEST_POST_NOTIFICATION_PERMISSION import org.kiwix.kiwixmobile.core.utils.ServerUtils @@ -60,15 +62,15 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.StartServer +import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService +import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_CHECK_IP_ADDRESS +import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_START_SERVER +import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_STOP_SERVER import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BookOnDiskDelegate import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk -import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService -import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_CHECK_IP_ADDRESS -import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_START_SERVER -import org.kiwix.kiwixmobile.core.webserver.wifi_hotspot.HotspotService.Companion.ACTION_STOP_SERVER import javax.inject.Inject class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { @@ -87,6 +89,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { @Inject lateinit var zimReaderFactory: ZimFileReader.Factory + @Inject + lateinit var zimReaderContainer: ZimReaderContainer + private lateinit var booksAdapter: BooksOnDiskAdapter private lateinit var bookDelegate: BookOnDiskDelegate.BookDelegate private var hotspotService: HotspotService? = null @@ -479,21 +484,19 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { @Suppress("NestedBlockDepth") override fun addBooks(books: List) { // Check if this is the app module, as custom apps may have multiple package names - if (requireActivity().packageName == "org.kiwix.kiwixmobile") { + if (!requireActivity().isCustomApp()) { booksAdapter.items = books } else { val updatedBooksList: MutableList = arrayListOf() books.forEach { if (it is BookOnDisk) { - zimReaderFactory.create(it.file)?.let { zimFileReader -> + zimReaderContainer.zimFileReader?.let { zimFileReader -> val booksOnDiskListItem = - (BookOnDisk(zimFileReader.zimFile, zimFileReader) as BooksOnDiskListItem) + (BookOnDisk(it.file, zimFileReader) as BooksOnDiskListItem) .apply { isSelected = true } - updatedBooksList.add(booksOnDiskListItem).also { - zimFileReader.dispose() - } + updatedBooksList.add(booksOnDiskListItem) } } else { updatedBooksList.add(it) diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index 7b042594be..e743c213ab 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -49,6 +49,10 @@ android { } } assetPacks += ":install_time_asset" + androidResources { + // to not compress zim file in asset folder + noCompress.add("zim") + } } fun ProductFlavor.createDownloadTask(file: File): Task { diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt index 73cad95ca8..c689872de0 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt @@ -20,15 +20,14 @@ package org.kiwix.kiwixmobile.custom.main import android.content.Context import android.content.pm.PackageManager +import android.content.res.AssetFileDescriptor import android.util.Log import androidx.core.content.ContextCompat -import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.custom.BuildConfig import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile import org.kiwix.kiwixmobile.custom.main.ValidationState.HasNothing import java.io.File -import java.io.FileOutputStream import java.io.IOException import javax.inject.Inject @@ -44,10 +43,10 @@ class CustomFileValidator @Inject constructor(private val context: Context) { private fun detectInstallationState( obbFiles: List = obbFiles(), zimFiles: List = zimFiles(), - assetFile: File? = getFileFromPlayAssetDelivery() + assetFileDescriptor: AssetFileDescriptor? = getAssetFileDescriptorFromPlayAssetDelivery() ): ValidationState { return when { - assetFile != null -> HasFile(assetFile) + assetFileDescriptor != null -> HasFile(null, assetFileDescriptor) obbFiles.isNotEmpty() && zimFiles().isNotEmpty() -> HasBothFiles(obbFiles[0], zimFiles[0]) obbFiles.isNotEmpty() -> HasFile(obbFiles[0]) zimFiles.isNotEmpty() -> HasFile(zimFiles[0]) @@ -55,31 +54,12 @@ class CustomFileValidator @Inject constructor(private val context: Context) { } } - @Suppress("NestedBlockDepth", "MagicNumber") - private fun getFileFromPlayAssetDelivery(): File? { - var zimFile: File? = null + @Suppress("MagicNumber") + private fun getAssetFileDescriptorFromPlayAssetDelivery(): AssetFileDescriptor? { try { val context = context.createPackageContext(context.packageName, 0) val assetManager = context.assets - val inputStream = assetManager.open(BuildConfig.PLAY_ASSET_FILE) - val filePath = ContextCompat.getExternalFilesDirs(context, null)[0] - zimFile = File(filePath, BuildConfig.PLAY_ASSET_FILE) - // Write zim file data if file does not exist or corrupted - if (!zimFile.isFileExist() || zimFile.length() == 0L) { - // Delete previously corrupted file - if (zimFile.isFileExist()) zimFile.delete() - zimFile.createNewFile() - FileOutputStream(zimFile).use { outputSteam -> - inputStream.use { inputStream -> - val buffer = ByteArray(1024) - var length: Int - while (inputStream.read(buffer).also { length = it } > 0) { - outputSteam.write(buffer, 0, length) - } - outputSteam.flush() - } - } - } + return assetManager.openFd(BuildConfig.PLAY_ASSET_FILE) } catch (packageNameNotFoundException: PackageManager.NameNotFoundException) { Log.w( "ASSET_PACKAGE_DELIVERY", @@ -88,7 +68,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) { } catch (ioException: IOException) { Log.w("ASSET_PACKAGE_DELIVERY", "Unable to copy the content of asset $ioException") } - return zimFile + return null } private fun obbFiles() = scanDirs(ContextCompat.getObbDirs(context), "obb") @@ -124,6 +104,8 @@ class CustomFileValidator @Inject constructor(private val context: Context) { sealed class ValidationState { data class HasBothFiles(val obbFile: File, val zimFile: File) : ValidationState() - data class HasFile(val file: File) : ValidationState() + data class HasFile(val file: File?, val assetFileDescriptor: AssetFileDescriptor? = null) : + ValidationState() + object HasNothing : ValidationState() } diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt index 3c0d1fca3d..308506cb93 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt @@ -95,6 +95,16 @@ class CustomMainActivity : CoreMainActivity() { override fun setupDrawerToggle(toolbar: Toolbar) { super.setupDrawerToggle(toolbar) activityCustomMainBinding.drawerNavView.apply { + /** + * Hide the 'ZimHostFragment' option from the navigation menu + * because we are now using fd (FileDescriptor) + * to read the zim file from the asset folder. Currently, + * 'KiwixServer' is unable to host zim files via fd. + * This feature is temporarily hidden for custom apps. + * We will re-enable it for custom apps once the issue is resolved. + * For more info see https://github.com/kiwix/kiwix-android/pull/3516 + */ + menu.findItem(R.id.menu_host_books)?.isVisible = false setNavigationItemSelectedListener { item -> closeNavigationDrawer() onNavigationItemSelected(item) diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt index 090bac52d5..20672ff4ee 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt @@ -32,6 +32,7 @@ import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigationResult import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setupDrawerToggle +import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.main.CoreReaderFragment import org.kiwix.kiwixmobile.core.main.FIND_IN_PAGE_SEARCH_STRING import org.kiwix.kiwixmobile.core.main.MainMenu @@ -39,11 +40,14 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchItemToOpen import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower +import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getDemoFilePathForCustomApp import org.kiwix.kiwixmobile.core.utils.titleToUrl import org.kiwix.kiwixmobile.core.utils.urlSuffixToParsableUrl +import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.custom.BuildConfig import org.kiwix.kiwixmobile.custom.R import org.kiwix.kiwixmobile.custom.customActivityComponent +import java.io.File import java.util.Locale import javax.inject.Inject @@ -143,7 +147,22 @@ class CustomReaderFragment : CoreReaderFragment() { customFileValidator.validate( onFilesFound = { when (it) { - is ValidationState.HasFile -> openZimFile(it.file, true) + is ValidationState.HasFile -> { + if (it.assetFileDescriptor != null) { + openZimFile(null, true, it.assetFileDescriptor) + } else { + openZimFile(it.file, true) + } + // Save book in the database to display it in `ZimHostFragment`. + zimReaderContainer?.zimFileReader?.let { zimFileReader -> + // Check if the file is not null. If the file is null, + // it means we have created zimFileReader with a fileDescriptor, + // so we create a demo file to save it in the database for display on the `ZimHostFragment`. + val file = it.file ?: createDemoFile() + val bookOnDisk = BookOnDisk(file, zimFileReader) + repositoryActions?.saveBook(bookOnDisk) + } + } is ValidationState.HasBothFiles -> { it.zimFile.delete() openZimFile(it.obbFile, true) @@ -157,6 +176,11 @@ class CustomReaderFragment : CoreReaderFragment() { ) } + private fun createDemoFile() = + File(getDemoFilePathForCustomApp(requireActivity())).also { + if (!it.isFileExist()) it.createNewFile() + } + @Suppress("DEPRECATION") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater)