From 5104c49f8e18101e23214a3e43b583dfcf61b55d Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 11 Jul 2024 18:01:52 +0530 Subject: [PATCH 01/41] Replacing the fetch library with Android's DownloadManager. --- .../library/OnlineLibraryFragment.kt | 22 +++++++ .../kiwixmobile/core/dao/FetchDownloadDao.kt | 30 ++++++--- .../core/dao/entities/FetchDownloadEntity.kt | 3 +- .../core/di/modules/ApplicationModule.kt | 11 +++- .../core/di/modules/CoreServiceModule.kt | 9 +++ .../core/di/modules/DatabaseModule.kt | 6 +- .../core/di/modules/DownloaderModule.kt | 23 +++++-- .../DownloadManagerBroadcastReceiver.kt | 38 +++++++++++ .../downloadManager/DownloadManagerMonitor.kt | 47 ++++++++++++++ .../DownloadManagerRequester.kt | 63 +++++++++++++++++++ .../downloader/fetch/FetchDownloadMonitor.kt | 2 +- .../fetch/FetchDownloadRequester.kt | 2 +- .../core/downloader/model/DownloadRequest.kt | 11 +++- 13 files changed, 241 insertions(+), 26 deletions(-) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index d86661cf18..8054305458 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -59,6 +59,7 @@ import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.downloader.Downloader +import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isManageExternalStoragePermissionGranted import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate @@ -89,6 +90,7 @@ import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.SelectFolder import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.YesNoDialog.WifiOnly import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getPathFromUri import org.kiwix.kiwixmobile.databinding.FragmentDestinationDownloadBinding +import org.kiwix.kiwixmobile.zimManager.Fat32Checker import org.kiwix.kiwixmobile.zimManager.NetworkState import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel import org.kiwix.kiwixmobile.zimManager.libraryView.AvailableSpaceCalculator @@ -209,6 +211,26 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { } } ) + // + val libraryBookEntity = LibraryNetworkEntity.Book().apply { + id = "6a5413cd-0d96-7288-9c1b-5beda035e472" + size = "170628" + url = "https://download.kiwix.org/zim/zimit/100r.co_en_all_2024-06.zim.meta4" + mediaCount = "1433" + articleCount = "320" + favicon = + "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" + title = "100 Rabbits" + description = "Research and test low-tech solutions, and document findings" + language = "eng" + creator = "-" + publisher = "openZIM" + bookName = "100r.co_en_all" + tags = "_ftindex:yes;preppers;_category:other;_pictures:yes;_videos:yes;_details:yes" + date = "2024-06-24" + faviconMimeType = "image/png" + } + downloader.download(libraryBookEntity) } private fun setupMenu() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt index d2f8869ce3..733e78ae99 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt @@ -31,12 +31,16 @@ import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.core.extensions.deleteFile +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk +import java.io.File import javax.inject.Inject class FetchDownloadDao @Inject constructor( private val box: Box, - private val newBookDao: NewBookDao + private val newBookDao: NewBookDao, + private val sharedPreferenceUtil: SharedPreferenceUtil ) { fun downloads(): Flowable> = @@ -58,7 +62,7 @@ class FetchDownloadDao @Inject constructor( fun update(download: Download) { box.store.callInTx { - getEntityFor(download)?.let { dbEntity -> + getEntityFor(download.id)?.let { dbEntity -> dbEntity.updateWith(download) .takeIf { updatedEntity -> updatedEntity != dbEntity } ?.let(box::put) @@ -66,9 +70,9 @@ class FetchDownloadDao @Inject constructor( } } - private fun getEntityFor(download: Download) = + private fun getEntityFor(downloadId: Int) = box.query { - equal(FetchDownloadEntity_.downloadId, download.id) + equal(FetchDownloadEntity_.downloadId, downloadId) }.find().getOrNull(0) fun getEntityForFileName(fileName: String) = @@ -79,13 +83,17 @@ class FetchDownloadDao @Inject constructor( ) }.findFirst() - fun insert(downloadId: Long, book: Book) { - box.put(FetchDownloadEntity(downloadId, book)) + fun insert(downloadId: Long, book: Book, filePath: String?) { + box.put(FetchDownloadEntity(downloadId, book, filePath)) } - fun delete(download: Download) { + fun delete(downloadId: Long) { + // remove the previous file from storage since we have cancelled the download. + // getEntityFor(downloadId.toInt())?.file?.let { + // File(it).deleteFile() + // } box.query { - equal(FetchDownloadEntity_.downloadId, download.id) + equal(FetchDownloadEntity_.downloadId, downloadId) }.remove() } @@ -96,9 +104,11 @@ class FetchDownloadDao @Inject constructor( ) { box.store.callInTx { if (doesNotAlreadyExist(book)) { + val downloadRequest = DownloadRequest(url, book.title) insert( - downloadRequester.enqueue(DownloadRequest(url)), - book = book + downloadRequester.enqueue(downloadRequest), + book = book, + filePath = downloadRequest.getDestinationFile(sharedPreferenceUtil).path ) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt index 12530caff2..a86ab66d5e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt @@ -54,7 +54,8 @@ data class FetchDownloadEntity( val favIcon: String, val tags: String? = null ) { - constructor(downloadId: Long, book: Book) : this( + constructor(downloadId: Long, book: Book, file: String?) : this( + file = file, downloadId = downloadId, bookId = book.id, title = book.title, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt index 5384219ed5..c8c6f8da91 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt @@ -36,7 +36,7 @@ import org.kiwix.kiwixmobile.core.di.qualifiers.Computation import org.kiwix.kiwixmobile.core.di.qualifiers.IO import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor -import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadMonitor +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.utils.BookUtils import javax.inject.Singleton @@ -88,10 +88,15 @@ class ApplicationModule { @Provides fun provideComputationThread(): Scheduler = Schedulers.computation() + // @Provides + // @Singleton + // internal fun provideDownloadMonitor(fetchDownloadMonitor: FetchDownloadMonitor): DownloadMonitor = + // fetchDownloadMonitor + @Provides @Singleton - internal fun provideDownloadMonitor(fetchDownloadMonitor: FetchDownloadMonitor): DownloadMonitor = - fetchDownloadMonitor + internal fun provideDownloadMonitor(downloadManagerMonitor: DownloadManagerMonitor) + : DownloadMonitor = downloadManagerMonitor @Provides @Singleton diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt index dc2b6b6e4e..c7dd8db3cf 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt @@ -25,6 +25,8 @@ import dagger.Module import dagger.Provides import org.kiwix.kiwixmobile.core.di.CoreServiceScope import org.kiwix.kiwixmobile.core.qr.GenerateQR +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor import org.kiwix.kiwixmobile.core.read_aloud.ReadAloudNotificationManger import org.kiwix.kiwixmobile.core.webserver.KiwixServer import org.kiwix.kiwixmobile.core.webserver.WebServerHelper @@ -75,4 +77,11 @@ class CoreServiceModule { @Provides @CoreServiceScope fun providesGenerateQr(): GenerateQR = GenerateQR() + fun providesDownloadManagerBroadcastReceiver(callback: DownloadManagerBroadcastReceiver.Callback) + : DownloadManagerBroadcastReceiver = DownloadManagerBroadcastReceiver(callback) + + @Provides + @CoreServiceScope + fun providesDownloadInformationCallback(downloadManagerMonitor: DownloadManagerMonitor) + : DownloadManagerBroadcastReceiver.Callback = downloadManagerMonitor } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt index c781829b65..d268420f17 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt @@ -32,6 +32,7 @@ import org.kiwix.kiwixmobile.core.dao.NewNoteDao import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao import org.kiwix.kiwixmobile.core.dao.entities.MyObjectBox import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import javax.inject.Singleton @Module @@ -71,9 +72,10 @@ open class DatabaseModule { @Provides @Singleton fun providesFetchDownloadDao( boxStore: BoxStore, - newBookDao: NewBookDao + newBookDao: NewBookDao, + sharedPreferenceUtil: SharedPreferenceUtil ): FetchDownloadDao = - FetchDownloadDao(boxStore.boxFor(), newBookDao) + FetchDownloadDao(boxStore.boxFor(), newBookDao, sharedPreferenceUtil) @Singleton @Provides diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 783fc8eeff..5f5a1a2c10 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -17,6 +17,7 @@ */ package org.kiwix.kiwixmobile.core.di.modules +import android.app.DownloadManager import android.content.Context import com.tonyodev.fetch2.Fetch import com.tonyodev.fetch2.Fetch.Impl @@ -33,8 +34,8 @@ import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.Downloader import org.kiwix.kiwixmobile.core.downloader.DownloaderImpl +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerRequester import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadNotificationManager -import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadRequester import org.kiwix.kiwixmobile.core.utils.CONNECT_TIME_OUT import org.kiwix.kiwixmobile.core.utils.READ_TIME_OUT import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil @@ -51,10 +52,10 @@ object DownloaderModule { kiwixService: KiwixService ): Downloader = DownloaderImpl(downloadRequester, downloadDao, kiwixService) - @Provides - @Singleton - fun providesDownloadRequester(fetch: Fetch, sharedPreferenceUtil: SharedPreferenceUtil): - DownloadRequester = FetchDownloadRequester(fetch, sharedPreferenceUtil) + // @Provides + // @Singleton + // fun providesDownloadRequester(fetch: Fetch, sharedPreferenceUtil: SharedPreferenceUtil): + // DownloadRequester = FetchDownloadRequester(fetch, sharedPreferenceUtil) @Provides @Singleton @@ -93,4 +94,16 @@ object DownloaderModule { @Singleton fun provideFetchDownloadNotificationManager(context: Context, fetchDownloadDao: FetchDownloadDao): FetchNotificationManager = FetchDownloadNotificationManager(context, fetchDownloadDao) + + @Provides + @Singleton + fun providesDownloadRequester( + downloadManager: DownloadManager, + sharedPreferenceUtil: SharedPreferenceUtil, + fetchDownloadDao: FetchDownloadDao + ): DownloadRequester = DownloadManagerRequester( + downloadManager, + sharedPreferenceUtil, + fetchDownloadDao + ) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt new file mode 100644 index 0000000000..48cf414884 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt @@ -0,0 +1,38 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +import android.app.DownloadManager.ACTION_DOWNLOAD_COMPLETE +import android.content.Context +import android.content.Intent +import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver +import javax.inject.Inject + +class DownloadManagerBroadcastReceiver @Inject constructor(private val callback: Callback) : + BaseBroadcastReceiver() { + // This broadcast will trigger when a download is completed or cancelled. + override val action: String = ACTION_DOWNLOAD_COMPLETE + + override fun onIntentWithActionReceived(context: Context, intent: Intent) { + } + + interface Callback { + fun downloadInformation() + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt new file mode 100644 index 0000000000..9d53dbb27d --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -0,0 +1,47 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +import android.app.DownloadManager +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.PublishSubject +import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor +import javax.inject.Inject + +class DownloadManagerMonitor @Inject constructor( + private val downloadManager: DownloadManager, + private val fetchDownloadDao: FetchDownloadDao +) : + DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { + private val updater = PublishSubject.create<() -> Unit>() + + override fun downloadInformation() {} + + init { + updater.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe( + { it.invoke() }, + Throwable::printStackTrace + ) + } + + override fun init() { + // empty method to so class does not get reported unused} + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt new file mode 100644 index 0000000000..2e1cd15a1f --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -0,0 +1,63 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +import android.app.DownloadManager +import android.app.DownloadManager.Request +import android.net.Uri +import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import org.kiwix.kiwixmobile.core.downloader.DownloadRequester +import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import javax.inject.Inject + +class DownloadManagerRequester @Inject constructor( + private val downloadManager: DownloadManager, + private val sharedPreferenceUtil: SharedPreferenceUtil, + private val fetchDownloadDao: FetchDownloadDao +) : DownloadRequester { + override fun enqueue(downloadRequest: DownloadRequest): Long = + downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) + + override fun cancel(downloadId: Long) { + downloadManager.remove(downloadId).also { + fetchDownloadDao.delete(downloadId) + } + } + + override fun retryDownload(downloadId: Long) { + } + + override fun pauseResumeDownload(downloadId: Long, isPause: Boolean) { + } +} + +private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: SharedPreferenceUtil) = + DownloadManager.Request(uri).apply { + setDestinationUri(Uri.fromFile(getDestinationFile(sharedPreferenceUtil))) + setAllowedNetworkTypes( + if (sharedPreferenceUtil.prefWifiOnly) + Request.NETWORK_WIFI + else + Request.NETWORK_MOBILE + ) + setTitle(notificationTitle) + setAllowedOverMetered(true) + setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt index eef4bf97d8..32db1a74b0 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt @@ -101,7 +101,7 @@ class FetchDownloadMonitor @Inject constructor(fetch: Fetch, fetchDownloadDao: F } private fun delete(download: Download) { - updater.onNext { fetchDownloadDao.delete(download) } + updater.onNext { fetchDownloadDao.delete(download.id.toLong()) } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt index 13872f8e72..3da6946d07 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt @@ -55,7 +55,7 @@ class FetchDownloadRequester @Inject constructor( } private fun DownloadRequest.toFetchRequest(sharedPreferenceUtil: SharedPreferenceUtil) = - Request("$uri", getDestination(sharedPreferenceUtil)).apply { + Request("$uri", getDestinationFile(sharedPreferenceUtil).path).apply { networkType = if (sharedPreferenceUtil.prefWifiOnly) WIFI_ONLY else ALL autoRetryMaxAttempts = AUTO_RETRY_MAX_ATTEMPTS } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt index d060807334..9e91097141 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt @@ -18,13 +18,18 @@ package org.kiwix.kiwixmobile.core.downloader.model import android.net.Uri +import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.StorageUtils +import java.io.File -data class DownloadRequest(val urlString: String) { +data class DownloadRequest(val urlString: String, val notificationTitle: String) { val uri: Uri get() = Uri.parse(urlString) - fun getDestination(sharedPreferenceUtil: SharedPreferenceUtil): String = - "${sharedPreferenceUtil.prefStorage}/Kiwix/${StorageUtils.getFileNameFromUrl(urlString)}" + fun getDestinationFile(sharedPreferenceUtil: SharedPreferenceUtil): File { + val file = + File("${sharedPreferenceUtil.prefStorage}/Kiwix/${StorageUtils.getFileNameFromUrl(urlString)}") + return file + } } From 2616fa8565de064099f3e83001844963d686ffee Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 12 Jul 2024 15:19:39 +0530 Subject: [PATCH 02/41] Updating the current download item on OnlineLibraryScreen if a download completes or cancelled --- .../library/OnlineLibraryFragment.kt | 2 +- .../kiwixmobile/core/dao/DownloadRoomDao.kt | 30 +++++++ .../kiwixmobile/core/dao/FetchDownloadDao.kt | 8 +- .../core/di/components/CoreComponent.kt | 3 + .../core/di/modules/CoreServiceModule.kt | 2 +- .../core/di/modules/DownloaderModule.kt | 12 +++ .../DownloadManagerBroadcastReceiver.kt | 4 +- .../downloadManager/DownloadManagerMonitor.kt | 81 ++++++++++++++++++- .../kiwixmobile/core/main/CoreMainActivity.kt | 12 +++ 9 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index 8054305458..3cc732db7b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -230,7 +230,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { date = "2024-06-24" faviconMimeType = "image/png" } - downloader.download(libraryBookEntity) + // downloader.download(libraryBookEntity) } private fun setupMenu() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt new file mode 100644 index 0000000000..511234fa85 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt @@ -0,0 +1,30 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.dao + +import androidx.room.Dao +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import javax.inject.Inject + +// @Dao +// abstract class DownloadRoomDao @Inject constructor( +// private val newBookDao: NewBookDao, +// private val sharedPreferenceUtil: SharedPreferenceUtil +// ) { +// } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt index 733e78ae99..ec017c2ee4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt @@ -70,7 +70,7 @@ class FetchDownloadDao @Inject constructor( } } - private fun getEntityFor(downloadId: Int) = + fun getEntityFor(downloadId: Int) = box.query { equal(FetchDownloadEntity_.downloadId, downloadId) }.find().getOrNull(0) @@ -89,9 +89,9 @@ class FetchDownloadDao @Inject constructor( fun delete(downloadId: Long) { // remove the previous file from storage since we have cancelled the download. - // getEntityFor(downloadId.toInt())?.file?.let { - // File(it).deleteFile() - // } + getEntityFor(downloadId.toInt())?.file?.let { + File(it).deleteFile() + } box.query { equal(FetchDownloadEntity_.downloadId, downloadId) }.remove() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index 674ebfeb8e..cb4e32713d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -51,6 +51,7 @@ import org.kiwix.kiwixmobile.core.di.modules.MutexModule import org.kiwix.kiwixmobile.core.di.modules.NetworkModule import org.kiwix.kiwixmobile.core.di.modules.SearchModule import org.kiwix.kiwixmobile.core.downloader.Downloader +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.reader.ZimFileReader @@ -114,6 +115,8 @@ interface CoreComponent { fun searchResultGenerator(): SearchResultGenerator fun mutex(): Mutex + fun downloadManagerBroadCastReceiver(): DownloadManagerBroadcastReceiver + fun inject(application: CoreApp) fun inject(kiwixWebView: KiwixWebView) fun inject(storageSelectDialog: StorageSelectDialog) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt index c7dd8db3cf..2d605cf701 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt @@ -24,9 +24,9 @@ import android.content.Context import dagger.Module import dagger.Provides import org.kiwix.kiwixmobile.core.di.CoreServiceScope -import org.kiwix.kiwixmobile.core.qr.GenerateQR import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor +import org.kiwix.kiwixmobile.core.qr.GenerateQR import org.kiwix.kiwixmobile.core.read_aloud.ReadAloudNotificationManger import org.kiwix.kiwixmobile.core.webserver.KiwixServer import org.kiwix.kiwixmobile.core.webserver.WebServerHelper diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 5f5a1a2c10..a416c7317f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -34,6 +34,8 @@ import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.Downloader import org.kiwix.kiwixmobile.core.downloader.DownloaderImpl +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerRequester import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadNotificationManager import org.kiwix.kiwixmobile.core.utils.CONNECT_TIME_OUT @@ -106,4 +108,14 @@ object DownloaderModule { sharedPreferenceUtil, fetchDownloadDao ) + + @Provides + @Singleton + fun provideDownloadManagerCallback(downloadManagerMonitor: DownloadManagerMonitor) + : DownloadManagerBroadcastReceiver.Callback = downloadManagerMonitor + + @Provides + @Singleton + fun providesDownloadManagerBroadcastReceiver(callback: DownloadManagerBroadcastReceiver.Callback) + : DownloadManagerBroadcastReceiver = DownloadManagerBroadcastReceiver(callback) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt index 48cf414884..8cfe917c19 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt @@ -21,6 +21,7 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.app.DownloadManager.ACTION_DOWNLOAD_COMPLETE import android.content.Context import android.content.Intent +import android.util.Log import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver import javax.inject.Inject @@ -30,9 +31,10 @@ class DownloadManagerBroadcastReceiver @Inject constructor(private val callback: override val action: String = ACTION_DOWNLOAD_COMPLETE override fun onIntentWithActionReceived(context: Context, intent: Intent) { + callback.downloadInformation(intent) } interface Callback { - fun downloadInformation() + fun downloadInformation(intent: Intent) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 9d53dbb27d..da501ba6e9 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -19,6 +19,8 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.app.DownloadManager +import android.content.Intent +import android.util.Log import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao @@ -31,8 +33,85 @@ class DownloadManagerMonitor @Inject constructor( ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val updater = PublishSubject.create<() -> Unit>() + private val lock = Any() - override fun downloadInformation() {} + override fun downloadInformation(intent: Intent) { + synchronized(lock) { + intent.extras?.let { + val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) + if (downloadedFileId != -1L) { + val query = DownloadManager.Query().setFilterById(downloadedFileId) + val cursor = downloadManager.query(query).also { cursor -> + Log.e("HELLO", "downloadInformation: ${cursor.moveToFirst()}") + } + if (cursor.moveToFirst()) { + val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) + val status = cursor.getInt(columnIndex) + val columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON) + val reason = cursor.getInt(columnReason) + + when (status) { + DownloadManager.STATUS_FAILED -> { + var failedReason = "" + when (reason) { + DownloadManager.ERROR_CANNOT_RESUME -> failedReason = "ERROR_CANNOT_RESUME" + DownloadManager.ERROR_DEVICE_NOT_FOUND -> failedReason = "ERROR_DEVICE_NOT_FOUND" + DownloadManager.ERROR_FILE_ALREADY_EXISTS -> failedReason = + "ERROR_FILE_ALREADY_EXISTS" + + DownloadManager.ERROR_FILE_ERROR -> failedReason = "ERROR_FILE_ERROR" + DownloadManager.ERROR_HTTP_DATA_ERROR -> failedReason = "ERROR_HTTP_DATA_ERROR" + DownloadManager.ERROR_INSUFFICIENT_SPACE -> failedReason = + "ERROR_INSUFFICIENT_SPACE" + + DownloadManager.ERROR_TOO_MANY_REDIRECTS -> failedReason = + "ERROR_TOO_MANY_REDIRECTS" + + DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> failedReason = + "ERROR_UNHANDLED_HTTP_CODE" + + DownloadManager.ERROR_UNKNOWN -> failedReason = "ERROR_UNKNOWN" + } + Log.e("STATUS", "FAILED: $failedReason") + } + + DownloadManager.STATUS_PAUSED -> { + var pausedReason = "" + + when (reason) { + DownloadManager.PAUSED_QUEUED_FOR_WIFI -> pausedReason = "PAUSED_QUEUED_FOR_WIFI" + DownloadManager.PAUSED_UNKNOWN -> pausedReason = "PAUSED_UNKNOWN" + DownloadManager.PAUSED_WAITING_FOR_NETWORK -> pausedReason = + "PAUSED_WAITING_FOR_NETWORK" + + DownloadManager.PAUSED_WAITING_TO_RETRY -> pausedReason = + "PAUSED_WAITING_TO_RETRY" + } + Log.e("STATUS", "STATUS_PAUSED: $pausedReason") + } + + DownloadManager.STATUS_PENDING -> Log.e( + "STATUS", + "STATUS_PENDING" + ) + + DownloadManager.STATUS_RUNNING -> Log.e("STATUS", "STATUS_RUNNING:") + + DownloadManager.STATUS_SUCCESSFUL -> { + Log.e("STATUS", "STATUS_SUCCESSFUL:") + // updater.onNext { fetchDownloadDao.update(download) } + downloadManager.remove(downloadedFileId) + } + } + } else { + // the download is cancelled + updater.onNext { fetchDownloadDao.delete(downloadedFileId) } + } + Log.e("HELLO", "downloadInformation: $downloadedFileId") + } + } + } + } init { updater.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt index 488bbc073a..49c2e0ee0c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt @@ -43,15 +43,18 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.BuildConfig import org.kiwix.kiwixmobile.core.CoreApp +import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToRoomMigrator import org.kiwix.kiwixmobile.core.di.components.CoreActivityComponent +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.extensions.browserIntent import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon +import org.kiwix.kiwixmobile.core.extensions.registerReceiver import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.search.NAV_ARG_SEARCH_STRING @@ -95,6 +98,9 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { @Inject lateinit var objectBoxToLibkiwixMigrator: ObjectBoxToLibkiwixMigrator @Inject lateinit var objectBoxToRoomMigrator: ObjectBoxToRoomMigrator + @Inject + lateinit var downloadManagerBroadcastReceiver: DownloadManagerBroadcastReceiver + override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.KiwixTheme) super.onCreate(savedInstanceState) @@ -125,6 +131,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { CoroutineScope(Dispatchers.IO).launch { objectBoxToRoomMigrator.migrateObjectBoxDataToRoom() } + downloadManagerBroadcastReceiver.let(::registerReceiver) } @Suppress("DEPRECATION") @@ -141,6 +148,11 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { } } + override fun onDestroy() { + downloadManagerBroadcastReceiver.let(::unregisterReceiver) + super.onDestroy() + } + open fun configureActivityBasedOn(destination: NavDestination) { if (destination.id !in topLevelDestinations) { handleDrawerOnNavigation() From cebbbabbd8dfceb9066b192409d9809dc3b63797 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 19 Jul 2024 18:53:51 +0530 Subject: [PATCH 03/41] Introduced Room database for managing the downloads. * Refactored the code to use the room database instead of objectbox database with fetch. --- .../library/OnlineLibraryFragment.kt | 2 +- .../zimManager/ZimManageViewModel.kt | 6 +- .../libraryView/AvailableSpaceCalculator.kt | 5 +- .../libraryView/adapter/LibraryListItem.kt | 2 +- .../libraryView/adapter/LibraryViewHolder.kt | 2 +- .../zimManager/ZimManageViewModelTest.kt | 7 +- core/objectbox-models/default.json | 14 +- core/objectbox-models/default.json.bak | 7 +- .../kiwix/kiwixmobile/core/StorageObserver.kt | 5 +- .../kiwixmobile/core/dao/DownloadRoomDao.kt | 104 ++++++++++++- .../kiwixmobile/core/dao/FetchDownloadDao.kt | 140 ++++++++---------- .../core/dao/entities/DownloadRoomEntity.kt | 116 +++++++++++++++ .../core/dao/entities/FetchDownloadEntity.kt | 26 +--- .../core/data/KiwixRoomDatabase.kt | 44 +++++- .../core/di/components/CoreComponent.kt | 2 + .../core/di/modules/DatabaseModule.kt | 4 + .../core/di/modules/DownloaderModule.kt | 13 +- .../core/downloader/DownloaderImpl.kt | 9 +- .../downloadManager/DownloadManagerMonitor.kt | 112 +++++++++----- .../DownloadManagerRequester.kt | 13 +- .../core/downloader/downloadManager/Error.kt | 59 ++++++++ .../core/downloader/downloadManager/Status.kt | 52 +++++++ .../downloader/fetch/FetchDownloadMonitor.kt | 4 +- .../fetch/FetchDownloadNotificationManager.kt | 4 +- .../core/downloader/model/DownloadItem.kt | 25 ++-- .../core/downloader/model/DownloadModel.kt | 12 +- .../adapter/BooksOnDiskListItem.kt | 8 +- .../kiwixmobile/core/StorageObserverTest.kt | 7 +- .../download/CustomDownloadViewModel.kt | 9 +- .../download/CustomDownloadViewModelTest.kt | 8 +- 30 files changed, 599 insertions(+), 222 deletions(-) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index 3cc732db7b..e63ce12fa0 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -50,7 +50,6 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.tonyodev.fetch2.Status import eu.mhutti1.utils.storage.StorageDevice import eu.mhutti1.utils.storage.StorageSelectDialog import org.kiwix.kiwixmobile.R @@ -59,6 +58,7 @@ import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.downloader.Downloader +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isManageExternalStoragePermissionGranted diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt index 15f66629c2..556178c010 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt @@ -37,7 +37,7 @@ import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.isWifi -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.data.DataSource @@ -88,7 +88,7 @@ const val MAX_PROGRESS = 100 private const val TAG_RX_JAVA_DEFAULT_ERROR_HANDLER = "RxJavaDefaultErrorHandler" class ZimManageViewModel @Inject constructor( - private val downloadDao: FetchDownloadDao, + private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, @@ -334,10 +334,12 @@ class ZimManageViewModel @Inject constructor( fromLocalesWithNetworkMatchesSetActiveBy( networkLanguageCounts(booksFromNetwork), defaultLanguage() ) + booksFromNetwork.isNotEmpty() && allLanguages.isNotEmpty() -> fromLocalesWithNetworkMatchesSetActiveBy( networkLanguageCounts(booksFromNetwork), allLanguages ) + else -> throw RuntimeException("Impossible state") } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt index 0d507f52b5..aac5522778 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt @@ -23,6 +23,7 @@ import eu.mhutti1.utils.storage.Kb import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book @@ -31,7 +32,7 @@ import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryListItem import javax.inject.Inject class AvailableSpaceCalculator @Inject constructor( - private val downloadDao: FetchDownloadDao, + private val downloadRoomDao: DownloadRoomDao, private val storageCalculator: StorageCalculator ) { private var availableSpaceCalculatorDisposable: Disposable? = null @@ -40,7 +41,7 @@ class AvailableSpaceCalculator @Inject constructor( successAction: (LibraryListItem.BookItem) -> Unit, failureAction: (String) -> Unit ) { - availableSpaceCalculatorDisposable = downloadDao.allDownloads() + availableSpaceCalculatorDisposable = downloadRoomDao.allDownloads() .map { it.map(DownloadModel::bytesRemaining).sum() } .map { bytesToBeDownloaded -> storageCalculator.availableBytes() - bytesToBeDownloaded } .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryListItem.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryListItem.kt index 67fdf091ad..ccc50e61e0 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryListItem.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryListItem.kt @@ -19,7 +19,7 @@ package org.kiwix.kiwixmobile.zimManager.libraryView.adapter import androidx.annotation.StringRes -import com.tonyodev.fetch2.Status +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status import org.kiwix.kiwixmobile.core.downloader.model.Base64String import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadState diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 7c1ec77424..17ac0c2407 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -19,9 +19,9 @@ package org.kiwix.kiwixmobile.zimManager.libraryView.adapter import android.view.View -import com.tonyodev.fetch2.Status import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.core.base.adapter.BaseViewHolder +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status import org.kiwix.kiwixmobile.core.downloader.model.Base64String import org.kiwix.kiwixmobile.core.extensions.setBitmap import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt index 4810efdc5f..d4212491c8 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt @@ -38,6 +38,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.StorageObserver +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao @@ -84,7 +85,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS @ExtendWith(InstantExecutorExtension::class) class ZimManageViewModelTest { - private val downloadDao: FetchDownloadDao = mockk() + private val downloadRoomDao: DownloadRoomDao = mockk() private val newBookDao: NewBookDao = mockk() private val newLanguagesDao: NewLanguagesDao = mockk() private val storageObserver: StorageObserver = mockk() @@ -127,7 +128,7 @@ class ZimManageViewModelTest { fun init() { clearAllMocks() every { connectivityBroadcastReceiver.action } returns "test" - every { downloadDao.downloads() } returns downloads + every { downloadRoomDao.downloads() } returns downloads every { newBookDao.books() } returns books every { storageObserver.getBooksOnFileSystem( @@ -144,7 +145,7 @@ class ZimManageViewModelTest { } returns networkCapabilities every { networkCapabilities.hasTransport(TRANSPORT_WIFI) } returns true viewModel = ZimManageViewModel( - downloadDao, + downloadRoomDao, newBookDao, newLanguagesDao, storageObserver, diff --git a/core/objectbox-models/default.json b/core/objectbox-models/default.json index 9f15e6f8cc..6a46ca54c7 100644 --- a/core/objectbox-models/default.json +++ b/core/objectbox-models/default.json @@ -339,16 +339,6 @@ "name": "totalSizeOfDownload", "type": 6 }, - { - "id": "19:3378789699620971394", - "name": "status", - "type": 5 - }, - { - "id": "20:6867355950440828062", - "name": "error", - "type": 5 - }, { "id": "21:5555873126720275555", "name": "file", @@ -478,7 +468,9 @@ 8819082642546094709, 7233601933599801875, 4335394620556092321, - 1899740026144478138 + 1899740026144478138, + 3378789699620971394, + 6867355950440828062 ], "retiredRelationUids": [], "version": 1 diff --git a/core/objectbox-models/default.json.bak b/core/objectbox-models/default.json.bak index d4ba5033d0..9f15e6f8cc 100644 --- a/core/objectbox-models/default.json.bak +++ b/core/objectbox-models/default.json.bak @@ -221,7 +221,7 @@ }, { "id": "7:7635075139296819361", - "lastPropertyId": "3:3320858395373055542", + "lastPropertyId": "4:6431198268026321201", "name": "RecentSearchEntity", "properties": [ { @@ -239,6 +239,11 @@ "id": "3:3320858395373055542", "name": "zimId", "type": 9 + }, + { + "id": "4:6431198268026321201", + "name": "url", + "type": 9 } ], "relations": [] diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt index 59ea755ea0..121e27651b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt @@ -22,6 +22,7 @@ import io.reactivex.Flowable import io.reactivex.functions.BiFunction import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.runBlocking +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel @@ -33,7 +34,7 @@ import java.io.File import javax.inject.Inject class StorageObserver @Inject constructor( - private val downloadDao: FetchDownloadDao, + private val downloadRoomDao: DownloadRoomDao, private val fileSearch: FileSearch, private val zimReaderFactory: ZimFileReader.Factory, private val libkiwixBookmarks: LibkiwixBookmarks @@ -43,7 +44,7 @@ class StorageObserver @Inject constructor( scanningProgressListener: ScanningProgressListener ): Flowable> { return scanFiles(scanningProgressListener) - .withLatestFrom(downloadDao.downloads(), BiFunction(::toFilesThatAreNotDownloading)) + .withLatestFrom(downloadRoomDao.downloads(), BiFunction(::toFilesThatAreNotDownloading)) .map { it.mapNotNull(::convertToBookOnDisk) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt index 511234fa85..59212fe4dd 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt @@ -19,12 +19,100 @@ package org.kiwix.kiwixmobile.core.dao import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import io.reactivex.Flowable +import io.reactivex.Single +import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity +import org.kiwix.kiwixmobile.core.downloader.DownloadRequester +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status +import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest +import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity +import org.kiwix.kiwixmobile.core.extensions.deleteFile import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil -import javax.inject.Inject - -// @Dao -// abstract class DownloadRoomDao @Inject constructor( -// private val newBookDao: NewBookDao, -// private val sharedPreferenceUtil: SharedPreferenceUtil -// ) { -// } +import java.io.File + +@Dao +abstract class DownloadRoomDao { + + @Query("SELECT * FROM DownloadRoomEntity") + abstract fun downloadRoomEntity(): Flowable> + + @Query("SELECT * FROM DownloadRoomEntity") + abstract fun getAllDownloads(): Single> + + fun downloads(): Flowable> = + downloadRoomEntity() + .distinctUntilChanged() + .doOnNext(::moveCompletedToBooksOnDiskDao) + .map { it.map(::DownloadModel) } + + fun allDownloads() = getAllDownloads().map { it.map(::DownloadModel) } + + private fun moveCompletedToBooksOnDiskDao(downloadEntities: List) { + downloadEntities.filter { it.status == Status.COMPLETED } + .takeIf(List::isNotEmpty) + ?.let { + deleteDownloadsList(it) + // newBookDao.insert(it.map(BooksOnDiskListItem::BookOnDisk)) + } + } + + fun update(downloadModel: DownloadModel) { + getEntityForDownloadId(downloadModel.downloadId)?.let { downloadRoomEntity -> + downloadRoomEntity.updateWith(downloadModel) + .takeIf { updatedEntity -> updatedEntity != downloadRoomEntity } + ?.let(::updateDownloadItem) + } + } + + @Update + abstract fun updateDownloadItem(downloadRoomEntity: DownloadRoomEntity) + + @Delete + abstract fun deleteDownloadsList(downloadRoomEntityList: List) + + @Query("DELETE FROM DownloadRoomEntity WHERE downloadId=:downloadId") + abstract fun deleteDownloadByDownloadId(downloadId: Long) + + @Query("SELECT * FROM DownloadRoomEntity WHERE downloadId=:downloadId") + abstract fun getEntityForDownloadId(downloadId: Long): DownloadRoomEntity? + + @Query("SELECT COUNT() FROM DownloadRoomEntity WHERE bookId = :bookId") + abstract fun count(bookId: String): Int + + @Insert + abstract fun saveDownload(downloadRoomEntity: DownloadRoomEntity) + + fun delete(downloadId: Long) { + // remove the previous file from storage since we have cancelled the download. + getEntityForDownloadId(downloadId)?.file?.let { + File(it).deleteFile() + } + deleteDownloadByDownloadId(downloadId) + } + + fun addIfDoesNotExist( + url: String, + book: LibraryNetworkEntity.Book, + downloadRequester: DownloadRequester, + sharedPreferenceUtil: SharedPreferenceUtil + ) { + if (doesNotAlreadyExist(book)) { + val downloadRequest = DownloadRequest(url, book.title) + saveDownload( + DownloadRoomEntity( + downloadRequester.enqueue(downloadRequest), + book = book, + file = downloadRequest.getDestinationFile(sharedPreferenceUtil).path + ) + ) + } + } + + private fun doesNotAlreadyExist(book: LibraryNetworkEntity.Book) = + count(book.id) == 0 +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt index ec017c2ee4..d3cde25293 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt @@ -17,24 +17,10 @@ */ package org.kiwix.kiwixmobile.core.dao -import com.tonyodev.fetch2.Download -import com.tonyodev.fetch2.Status.COMPLETED import io.objectbox.Box -import io.objectbox.kotlin.equal -import io.objectbox.kotlin.query -import io.objectbox.query.QueryBuilder -import io.reactivex.Flowable -import io.reactivex.Single import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity -import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity_ -import org.kiwix.kiwixmobile.core.downloader.DownloadRequester -import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel -import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book -import org.kiwix.kiwixmobile.core.extensions.deleteFile import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil -import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk -import java.io.File import javax.inject.Inject class FetchDownloadDao @Inject constructor( @@ -43,79 +29,79 @@ class FetchDownloadDao @Inject constructor( private val sharedPreferenceUtil: SharedPreferenceUtil ) { - fun downloads(): Flowable> = - box.asFlowable() - .distinctUntilChanged() - .doOnNext(::moveCompletedToBooksOnDiskDao) - .map { it.map(::DownloadModel) } + // fun downloads(): Flowable> = + // box.asFlowable() + // .distinctUntilChanged() + // .doOnNext(::moveCompletedToBooksOnDiskDao) + // .map { it.map(::DownloadModel) } - fun allDownloads() = Single.fromCallable { box.all.map(::DownloadModel) } + // fun allDownloads() = Single.fromCallable { box.all.map(::DownloadModel) } - private fun moveCompletedToBooksOnDiskDao(downloadEntities: List) { - downloadEntities.filter { it.status == COMPLETED }.takeIf { it.isNotEmpty() }?.let { - box.store.callInTx { - box.remove(it) - newBookDao.insert(it.map(::BookOnDisk)) - } - } - } + // private fun moveCompletedToBooksOnDiskDao(downloadEntities: List) { + // downloadEntities.filter { it.status == COMPLETED }.takeIf { it.isNotEmpty() }?.let { + // box.store.callInTx { + // box.remove(it) + // newBookDao.insert(it.map(::BookOnDisk)) + // } + // } + // } - fun update(download: Download) { - box.store.callInTx { - getEntityFor(download.id)?.let { dbEntity -> - dbEntity.updateWith(download) - .takeIf { updatedEntity -> updatedEntity != dbEntity } - ?.let(box::put) - } - } - } + // fun update(download: Download) { + // box.store.callInTx { + // getEntityFor(download.id)?.let { dbEntity -> + // dbEntity.updateWith(download) + // .takeIf { updatedEntity -> updatedEntity != dbEntity } + // ?.let(box::put) + // } + // } + // } - fun getEntityFor(downloadId: Int) = - box.query { - equal(FetchDownloadEntity_.downloadId, downloadId) - }.find().getOrNull(0) + // fun getEntityFor(downloadId: Int) = + // box.query { + // equal(FetchDownloadEntity_.downloadId, downloadId) + // }.find().getOrNull(0) - fun getEntityForFileName(fileName: String) = - box.query { - endsWith( - FetchDownloadEntity_.file, fileName, - QueryBuilder.StringOrder.CASE_INSENSITIVE - ) - }.findFirst() + // fun getEntityForFileName(fileName: String) = + // box.query { + // endsWith( + // FetchDownloadEntity_.file, fileName, + // QueryBuilder.StringOrder.CASE_INSENSITIVE + // ) + // }.findFirst() fun insert(downloadId: Long, book: Book, filePath: String?) { box.put(FetchDownloadEntity(downloadId, book, filePath)) } - fun delete(downloadId: Long) { - // remove the previous file from storage since we have cancelled the download. - getEntityFor(downloadId.toInt())?.file?.let { - File(it).deleteFile() - } - box.query { - equal(FetchDownloadEntity_.downloadId, downloadId) - }.remove() - } + // fun delete(downloadId: Long) { + // // remove the previous file from storage since we have cancelled the download. + // getEntityFor(downloadId.toInt())?.file?.let { + // File(it).deleteFile() + // } + // box.query { + // equal(FetchDownloadEntity_.downloadId, downloadId) + // }.remove() + // } - fun addIfDoesNotExist( - url: String, - book: Book, - downloadRequester: DownloadRequester - ) { - box.store.callInTx { - if (doesNotAlreadyExist(book)) { - val downloadRequest = DownloadRequest(url, book.title) - insert( - downloadRequester.enqueue(downloadRequest), - book = book, - filePath = downloadRequest.getDestinationFile(sharedPreferenceUtil).path - ) - } - } - } + // fun addIfDoesNotExist( + // url: String, + // book: Book, + // downloadRequester: DownloadRequester + // ) { + // box.store.callInTx { + // if (doesNotAlreadyExist(book)) { + // val downloadRequest = DownloadRequest(url, book.title) + // insert( + // downloadRequester.enqueue(downloadRequest), + // book = book, + // filePath = downloadRequest.getDestinationFile(sharedPreferenceUtil).path + // ) + // } + // } + // } - private fun doesNotAlreadyExist(book: Book) = - box.query { - equal(FetchDownloadEntity_.bookId, book.id, QueryBuilder.StringOrder.CASE_INSENSITIVE) - }.count() == 0L + // private fun doesNotAlreadyExist(book: Book) = + // box.query { + // equal(FetchDownloadEntity_.bookId, book.id, QueryBuilder.StringOrder.CASE_INSENSITIVE) + // }.count() == 0L } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt new file mode 100644 index 0000000000..37135dd722 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt @@ -0,0 +1,116 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.dao.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey +import io.objectbox.annotation.Convert +import io.objectbox.converter.PropertyConverter +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status +import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book + +@Entity +data class DownloadRoomEntity( + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + var downloadId: Long, + val file: String? = null, + val etaInMilliSeconds: Long = -1L, + val bytesDownloaded: Long = -1L, + val totalSizeOfDownload: Long = -1L, + @Convert(converter = StatusConverter::class, dbType = Int::class) + val status: Status = Status.NONE, + @Convert(converter = ErrorConverter::class, dbType = Int::class) + val error: Error = Error.NONE, + val progress: Int = -1, + val bookId: String, + val title: String, + val description: String?, + val language: String, + val creator: String, + val publisher: String, + val date: String, + val url: String?, + val articleCount: String?, + val mediaCount: String?, + val size: String, + val name: String?, + val favIcon: String, + val tags: String? = null +) { + constructor(downloadId: Long, book: Book, file: String?) : this( + file = file, + downloadId = downloadId, + bookId = book.id, + title = book.title, + description = book.description, + language = book.language, + creator = book.creator, + publisher = book.publisher, + date = book.date, + url = book.url, + articleCount = book.articleCount, + mediaCount = book.mediaCount, + size = book.size, + name = book.bookName, + favIcon = book.favicon, + tags = book.tags + ) + + fun toBook() = Book().apply { + id = bookId + title = this@DownloadRoomEntity.title + description = this@DownloadRoomEntity.description + language = this@DownloadRoomEntity.language + creator = this@DownloadRoomEntity.creator + publisher = this@DownloadRoomEntity.publisher + date = this@DownloadRoomEntity.date + url = this@DownloadRoomEntity.url + articleCount = this@DownloadRoomEntity.articleCount + mediaCount = this@DownloadRoomEntity.mediaCount + size = this@DownloadRoomEntity.size + bookName = name + favicon = favIcon + tags = this@DownloadRoomEntity.tags + } + + fun updateWith(download: DownloadModel) = copy( + file = download.file, + etaInMilliSeconds = download.etaInMilliSeconds, + bytesDownloaded = download.bytesDownloaded, + totalSizeOfDownload = download.totalSizeOfDownload, + status = download.state, + error = download.error, + progress = download.progress + ) +} + +class StatusConverter : EnumConverter() { + override fun convertToEntityProperty(databaseValue: Int) = Status.valueOf(databaseValue) +} + +class ErrorConverter : EnumConverter() { + override fun convertToEntityProperty(databaseValue: Int) = Error.valueOf(databaseValue) +} + +abstract class EnumConverter> : PropertyConverter { + override fun convertToDatabaseValue(entityProperty: E): Int = entityProperty.ordinal +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt index a86ab66d5e..825882da7e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt @@ -20,10 +20,8 @@ package org.kiwix.kiwixmobile.core.dao.entities import com.tonyodev.fetch2.Download import com.tonyodev.fetch2.Error import com.tonyodev.fetch2.Status -import io.objectbox.annotation.Convert import io.objectbox.annotation.Entity import io.objectbox.annotation.Id -import io.objectbox.converter.PropertyConverter import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book @Entity @@ -34,10 +32,10 @@ data class FetchDownloadEntity( val etaInMilliSeconds: Long = -1L, val bytesDownloaded: Long = -1L, val totalSizeOfDownload: Long = -1L, - @Convert(converter = StatusConverter::class, dbType = Int::class) - val status: Status = Status.NONE, - @Convert(converter = ErrorConverter::class, dbType = Int::class) - val error: Error = Error.NONE, + //@Convert(converter = StatusConverter::class, dbType = Int::class) + //val status: Status = Status.NONE, + //@Convert(converter = ErrorConverter::class, dbType = Int::class) + //val error: Error = Error.NONE, val progress: Int = -1, val bookId: String, val title: String, @@ -95,20 +93,8 @@ data class FetchDownloadEntity( etaInMilliSeconds = download.etaInMilliSeconds, bytesDownloaded = download.downloaded, totalSizeOfDownload = download.total, - status = download.status, - error = download.error, + // status = download.status, + // error = download.error, progress = download.progress ) } - -class StatusConverter : EnumConverter() { - override fun convertToEntityProperty(databaseValue: Int) = Status.valueOf(databaseValue) -} - -class ErrorConverter : EnumConverter() { - override fun convertToEntityProperty(databaseValue: Int) = Error.valueOf(databaseValue) -} - -abstract class EnumConverter> : PropertyConverter { - override fun convertToDatabaseValue(entityProperty: E): Int = entityProperty.ordinal -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt index 283c5e17cb..c144e5d18c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt @@ -25,10 +25,12 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDaoCoverts import org.kiwix.kiwixmobile.core.dao.NotesRoomDao import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao +import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.HistoryRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.NotesRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity @@ -38,9 +40,10 @@ import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity entities = [ RecentSearchRoomEntity::class, HistoryRoomEntity::class, - NotesRoomEntity::class + NotesRoomEntity::class, + DownloadRoomEntity::class ], - version = 3, + version = 4, exportSchema = false ) @TypeConverters(HistoryRoomDaoCoverts::class) @@ -48,6 +51,7 @@ abstract class KiwixRoomDatabase : RoomDatabase() { abstract fun recentSearchRoomDao(): RecentSearchRoomDao abstract fun historyRoomDao(): HistoryRoomDao abstract fun notesRoomDao(): NotesRoomDao + abstract fun downloadRoomDao(): DownloadRoomDao companion object { private var db: KiwixRoomDatabase? = null @@ -57,7 +61,7 @@ abstract class KiwixRoomDatabase : RoomDatabase() { ?: Room.databaseBuilder(context, KiwixRoomDatabase::class.java, "KiwixRoom.db") // We have already database name called kiwix.db in order to avoid complexity we named // as kiwixRoom.db - .addMigrations(MIGRATION_1_2, MIGRATION_2_3) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) .build() } } @@ -108,6 +112,40 @@ abstract class KiwixRoomDatabase : RoomDatabase() { } } + private val MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `DownloadRoomEntity` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `downloadId` INTEGER NOT NULL, + `file` TEXT, + `etaInMilliSeconds` INTEGER NOT NULL DEFAULT -1, + `bytesDownloaded` INTEGER NOT NULL DEFAULT -1, + `totalSizeOfDownload` INTEGER NOT NULL DEFAULT -1, + `status` TEXT NOT NULL DEFAULT 'NONE', + `error` TEXT NOT NULL DEFAULT 'NONE', + `progress` INTEGER NOT NULL DEFAULT -1, + `bookId` TEXT NOT NULL, + `title` TEXT NOT NULL, + `description` TEXT, + `language` TEXT NOT NULL, + `creator` TEXT NOT NULL, + `publisher` TEXT NOT NULL, + `date` TEXT NOT NULL, + `url` TEXT, + `articleCount` TEXT, + `mediaCount` TEXT, + `size` TEXT NOT NULL, + `name` TEXT, + `favIcon` TEXT NOT NULL, + `tags` TEXT + ) + """ + ) + } + } + fun destroyInstance() { db = null } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index cb4e32713d..9a1b764903 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -28,6 +28,7 @@ import eu.mhutti1.utils.storage.StorageSelectDialog import kotlinx.coroutines.sync.Mutex import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.StorageObserver +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.HistoryDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao @@ -100,6 +101,7 @@ interface CoreComponent { fun noteDao(): NewNoteDao fun newLanguagesDao(): NewLanguagesDao fun recentSearchDao(): NewRecentSearchDao + fun downloadRoomDao(): DownloadRoomDao fun newBookmarksDao(): NewBookmarksDao fun connectivityManager(): ConnectivityManager fun wifiManager(): WifiManager diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt index d268420f17..6afc532871 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt @@ -98,4 +98,8 @@ open class DatabaseModule { @Singleton @Provides fun provideNoteRoomDao(db: KiwixRoomDatabase) = db.notesRoomDao() + + @Singleton + @Provides + fun provideDownloadRoomDao(db: KiwixRoomDatabase) = db.downloadRoomDao() } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index a416c7317f..5fadc41737 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -28,6 +28,7 @@ import dagger.Module import dagger.Provides import okhttp3.OkHttpClient import org.kiwix.kiwixmobile.core.BuildConfig +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.data.remote.BasicAuthInterceptor import org.kiwix.kiwixmobile.core.data.remote.KiwixService @@ -50,9 +51,11 @@ object DownloaderModule { @Singleton fun providesDownloader( downloadRequester: DownloadRequester, - downloadDao: FetchDownloadDao, - kiwixService: KiwixService - ): Downloader = DownloaderImpl(downloadRequester, downloadDao, kiwixService) + downloadRoomDao: DownloadRoomDao, + kiwixService: KiwixService, + sharedPreferenceUtil: SharedPreferenceUtil + ): Downloader = + DownloaderImpl(downloadRequester, downloadRoomDao, kiwixService, sharedPreferenceUtil) // @Provides // @Singleton @@ -102,11 +105,11 @@ object DownloaderModule { fun providesDownloadRequester( downloadManager: DownloadManager, sharedPreferenceUtil: SharedPreferenceUtil, - fetchDownloadDao: FetchDownloadDao + downloadRoomDao: DownloadRoomDao ): DownloadRequester = DownloadManagerRequester( downloadManager, sharedPreferenceUtil, - fetchDownloadDao + downloadRoomDao ) @Provides diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt index 4036bf6cfa..6a6f6d0c1a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt @@ -20,16 +20,19 @@ package org.kiwix.kiwixmobile.core.downloader import android.annotation.SuppressLint import io.reactivex.Observable +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import javax.inject.Inject class DownloaderImpl @Inject constructor( private val downloadRequester: DownloadRequester, - private val downloadDao: FetchDownloadDao, - private val kiwixService: KiwixService + private val downloadRoomDao: DownloadRoomDao, + private val kiwixService: KiwixService, + private val sharedPreferenceUtil: SharedPreferenceUtil ) : Downloader { @SuppressLint("CheckResult") @@ -38,7 +41,7 @@ class DownloaderImpl @Inject constructor( .take(1) .subscribe( { - downloadDao.addIfDoesNotExist(it, book, downloadRequester) + downloadRoomDao.addIfDoesNotExist(it, book, downloadRequester, sharedPreferenceUtil) }, Throwable::printStackTrace ) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index da501ba6e9..14e8389887 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -23,13 +23,14 @@ import android.content.Intent import android.util.Log import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor +import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import javax.inject.Inject class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, - private val fetchDownloadDao: FetchDownloadDao + private val downloadRoomDao: DownloadRoomDao ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val updater = PublishSubject.create<() -> Unit>() @@ -41,9 +42,7 @@ class DownloadManagerMonitor @Inject constructor( val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) if (downloadedFileId != -1L) { val query = DownloadManager.Query().setFilterById(downloadedFileId) - val cursor = downloadManager.query(query).also { cursor -> - Log.e("HELLO", "downloadInformation: ${cursor.moveToFirst()}") - } + val cursor = downloadManager.query(query) if (cursor.moveToFirst()) { val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) val status = cursor.getInt(columnIndex) @@ -52,70 +51,103 @@ class DownloadManagerMonitor @Inject constructor( when (status) { DownloadManager.STATUS_FAILED -> { - var failedReason = "" + var error = Error.NONE when (reason) { - DownloadManager.ERROR_CANNOT_RESUME -> failedReason = "ERROR_CANNOT_RESUME" - DownloadManager.ERROR_DEVICE_NOT_FOUND -> failedReason = "ERROR_DEVICE_NOT_FOUND" - DownloadManager.ERROR_FILE_ALREADY_EXISTS -> failedReason = - "ERROR_FILE_ALREADY_EXISTS" + DownloadManager.ERROR_CANNOT_RESUME -> error = Error.ERROR_CANNOT_RESUME + DownloadManager.ERROR_DEVICE_NOT_FOUND -> error = Error.ERROR_DEVICE_NOT_FOUND + DownloadManager.ERROR_FILE_ALREADY_EXISTS -> error = + Error.ERROR_FILE_ALREADY_EXISTS - DownloadManager.ERROR_FILE_ERROR -> failedReason = "ERROR_FILE_ERROR" - DownloadManager.ERROR_HTTP_DATA_ERROR -> failedReason = "ERROR_HTTP_DATA_ERROR" - DownloadManager.ERROR_INSUFFICIENT_SPACE -> failedReason = - "ERROR_INSUFFICIENT_SPACE" + DownloadManager.ERROR_FILE_ERROR -> error = Error.ERROR_FILE_ERROR + DownloadManager.ERROR_HTTP_DATA_ERROR -> error = Error.ERROR_HTTP_DATA_ERROR + DownloadManager.ERROR_INSUFFICIENT_SPACE -> error = Error.ERROR_INSUFFICIENT_SPACE - DownloadManager.ERROR_TOO_MANY_REDIRECTS -> failedReason = - "ERROR_TOO_MANY_REDIRECTS" + DownloadManager.ERROR_TOO_MANY_REDIRECTS -> error = Error.ERROR_TOO_MANY_REDIRECTS - DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> failedReason = - "ERROR_UNHANDLED_HTTP_CODE" + DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> error = + Error.ERROR_UNHANDLED_HTTP_CODE - DownloadManager.ERROR_UNKNOWN -> failedReason = "ERROR_UNKNOWN" + DownloadManager.ERROR_UNKNOWN -> error = Error.UNKNOWN + } + updater.onNext { + updateDownloadStatus( + downloadedFileId, + Status.FAILED, + error + ) } - Log.e("STATUS", "FAILED: $failedReason") } DownloadManager.STATUS_PAUSED -> { - var pausedReason = "" - - when (reason) { - DownloadManager.PAUSED_QUEUED_FOR_WIFI -> pausedReason = "PAUSED_QUEUED_FOR_WIFI" - DownloadManager.PAUSED_UNKNOWN -> pausedReason = "PAUSED_UNKNOWN" - DownloadManager.PAUSED_WAITING_FOR_NETWORK -> pausedReason = - "PAUSED_WAITING_FOR_NETWORK" - - DownloadManager.PAUSED_WAITING_TO_RETRY -> pausedReason = - "PAUSED_WAITING_TO_RETRY" + updater.onNext { + updateDownloadStatus( + downloadedFileId, + Status.PAUSED, + Error.NONE + ) } - Log.e("STATUS", "STATUS_PAUSED: $pausedReason") } - DownloadManager.STATUS_PENDING -> Log.e( - "STATUS", - "STATUS_PENDING" - ) + DownloadManager.STATUS_PENDING -> { + updater.onNext { + updateDownloadStatus( + downloadedFileId, + Status.QUEUED, + Error.NONE + ) + } + } - DownloadManager.STATUS_RUNNING -> Log.e("STATUS", "STATUS_RUNNING:") + DownloadManager.STATUS_RUNNING -> { + updater.onNext { + updateDownloadStatus( + downloadedFileId, + Status.DOWNLOADING, + Error.NONE + ) + } + } DownloadManager.STATUS_SUCCESSFUL -> { - Log.e("STATUS", "STATUS_SUCCESSFUL:") - // updater.onNext { fetchDownloadDao.update(download) } + updater.onNext { + updateDownloadStatus( + downloadedFileId, + Status.COMPLETED, + Error.NONE + ) + } downloadManager.remove(downloadedFileId) } } } else { // the download is cancelled - updater.onNext { fetchDownloadDao.delete(downloadedFileId) } + updater.onNext { + updateDownloadStatus(downloadedFileId, Status.CANCELLED, Error.CANCELLED) + downloadRoomDao.delete(downloadedFileId) + } } + cursor.close() Log.e("HELLO", "downloadInformation: $downloadedFileId") } } } } + private fun updateDownloadStatus(downloadFileId: Long, status: Status, error: Error) { + downloadRoomDao.getEntityForDownloadId(downloadFileId)?.let { downloadEntity -> + val downloadModel = DownloadModel(downloadEntity).apply { + state = status + this.error = error + } + updater.onNext { downloadRoomDao.update(downloadModel) } + } + } + init { updater.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe( - { it.invoke() }, + { + synchronized(lock) { it.invoke() } + }, Throwable::printStackTrace ) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 2e1cd15a1f..62f6d02bc2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -21,7 +21,10 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.app.DownloadManager import android.app.DownloadManager.Request import android.net.Uri -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil @@ -30,14 +33,16 @@ import javax.inject.Inject class DownloadManagerRequester @Inject constructor( private val downloadManager: DownloadManager, private val sharedPreferenceUtil: SharedPreferenceUtil, - private val fetchDownloadDao: FetchDownloadDao + private val downloadRoomDao: DownloadRoomDao ) : DownloadRequester { override fun enqueue(downloadRequest: DownloadRequest): Long = downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) override fun cancel(downloadId: Long) { - downloadManager.remove(downloadId).also { - fetchDownloadDao.delete(downloadId) + CoroutineScope(Dispatchers.IO).launch { + downloadManager.remove(downloadId).also { + downloadRoomDao.delete(downloadId) + } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt new file mode 100644 index 0000000000..596095f245 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt @@ -0,0 +1,59 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +enum class Error(val value: Int) { + UNKNOWN(-1), + NONE(0), + NO_STORAGE_SPACE(1), + NO_NETWORK_CONNECTION(2), + UNKNOWN_IO_ERROR(3), + CANCELLED(4), + ERROR_CANNOT_RESUME(5), + ERROR_DEVICE_NOT_FOUND(6), + ERROR_FILE_ALREADY_EXISTS(7), + ERROR_FILE_ERROR(8), + ERROR_HTTP_DATA_ERROR(9), + ERROR_INSUFFICIENT_SPACE(10), + ERROR_TOO_MANY_REDIRECTS(11), + ERROR_UNHANDLED_HTTP_CODE(12); + + companion object { + @JvmStatic + fun valueOf(value: Int): Error { + return when (value) { + -1 -> UNKNOWN + 0 -> NONE + 1 -> NO_STORAGE_SPACE + 2 -> NO_NETWORK_CONNECTION + 3 -> UNKNOWN_IO_ERROR + 4 -> CANCELLED + 5 -> ERROR_CANNOT_RESUME + 6 -> ERROR_DEVICE_NOT_FOUND + 7 -> ERROR_FILE_ALREADY_EXISTS + 8 -> ERROR_FILE_ERROR + 9 -> ERROR_HTTP_DATA_ERROR + 10 -> ERROR_INSUFFICIENT_SPACE + 11 -> ERROR_TOO_MANY_REDIRECTS + 12 -> ERROR_UNHANDLED_HTTP_CODE + else -> UNKNOWN + } + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt new file mode 100644 index 0000000000..113ff3263f --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt @@ -0,0 +1,52 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +enum class Status(val value: Int) { + NONE(0), + QUEUED(1), + DOWNLOADING(2), + PAUSED(3), + COMPLETED(4), + CANCELLED(5), + FAILED(6), + REMOVED(7), + DELETED(8), + ADDED(9); + + companion object { + + @JvmStatic + fun valueOf(value: Int): Status { + return when (value) { + 0 -> NONE + 1 -> QUEUED + 2 -> DOWNLOADING + 3 -> PAUSED + 4 -> COMPLETED + 5 -> CANCELLED + 6 -> FAILED + 7 -> REMOVED + 8 -> DELETED + 9 -> ADDED + else -> NONE + } + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt index 32db1a74b0..5882d6c353 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt @@ -97,11 +97,11 @@ class FetchDownloadMonitor @Inject constructor(fetch: Fetch, fetchDownloadDao: F } private fun update(download: Download) { - updater.onNext { fetchDownloadDao.update(download) } + //updater.onNext { fetchDownloadDao.update(download) } } private fun delete(download: Download) { - updater.onNext { fetchDownloadDao.delete(download.id.toLong()) } + // updater.onNext { download.delete(download.id.toLong()) } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt index 2966513f91..cabf8c54b2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt @@ -84,9 +84,7 @@ class FetchDownloadNotificationManager( } else { android.R.drawable.stat_sys_download_done } - val notificationTitle = - fetchDownloadDao.getEntityForFileName(downloadNotification.title)?.title - ?: downloadNotification.title + val notificationTitle = downloadNotification.title notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setSmallIcon(smallIcon) .setContentTitle(notificationTitle) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt index ae9adf6871..98f66c1aba 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt @@ -18,19 +18,19 @@ package org.kiwix.kiwixmobile.core.downloader.model import android.content.Context -import com.tonyodev.fetch2.Error -import com.tonyodev.fetch2.Status -import com.tonyodev.fetch2.Status.ADDED -import com.tonyodev.fetch2.Status.CANCELLED -import com.tonyodev.fetch2.Status.COMPLETED -import com.tonyodev.fetch2.Status.DELETED -import com.tonyodev.fetch2.Status.DOWNLOADING -import com.tonyodev.fetch2.Status.FAILED -import com.tonyodev.fetch2.Status.NONE -import com.tonyodev.fetch2.Status.PAUSED -import com.tonyodev.fetch2.Status.QUEUED -import com.tonyodev.fetch2.Status.REMOVED import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.NONE +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.ADDED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.QUEUED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.DOWNLOADING +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.PAUSED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.COMPLETED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.CANCELLED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.FAILED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.REMOVED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.DELETED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error data class DownloadItem( val downloadId: Long, @@ -74,6 +74,7 @@ sealed class DownloadState( NONE, ADDED, QUEUED -> Pending + DOWNLOADING -> Running PAUSED -> Paused COMPLETED -> Successful diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt index a1261029aa..73416b639d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt @@ -17,9 +17,9 @@ */ package org.kiwix.kiwixmobile.core.downloader.model -import com.tonyodev.fetch2.Error -import com.tonyodev.fetch2.Status -import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity +import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.core.utils.StorageUtils @@ -30,15 +30,15 @@ data class DownloadModel( val etaInMilliSeconds: Long, val bytesDownloaded: Long, val totalSizeOfDownload: Long, - val state: Status, - val error: Error, + var state: Status, + var error: Error, val progress: Int, val book: Book ) { val bytesRemaining: Long by lazy { totalSizeOfDownload - bytesDownloaded } val fileNameFromUrl: String by lazy { StorageUtils.getFileNameFromUrl(book.url) } - constructor(downloadEntity: FetchDownloadEntity) : this( + constructor(downloadEntity: DownloadRoomEntity) : this( downloadEntity.id, downloadEntity.downloadId, downloadEntity.file, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt index 73ce4cc039..010c80b1f6 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt @@ -19,7 +19,7 @@ package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity -import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity +import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.zim_manager.KiwixTag @@ -58,9 +58,9 @@ sealed class BooksOnDiskListItem { bookOnDiskEntity.file ) - constructor(fetchDownloadEntity: FetchDownloadEntity) : this( - book = fetchDownloadEntity.toBook(), - file = File(fetchDownloadEntity.file) + constructor(downloadRoomEntity: DownloadRoomEntity) : this( + book = downloadRoomEntity.toBook(), + file = File(downloadRoomEntity.file) ) constructor(file: File, zimFileReader: ZimFileReader) : this( diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt index d3c80eeb36..235b508e9c 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel @@ -45,7 +46,7 @@ import java.io.File class StorageObserverTest { private val sharedPreferenceUtil: SharedPreferenceUtil = mockk() - private val downloadDao: FetchDownloadDao = mockk() + private val downloadRoomDao: DownloadRoomDao = mockk() private val fileSearch: FileSearch = mockk() private val downloadModel: DownloadModel = mockk() private val file: File = mockk() @@ -72,10 +73,10 @@ class StorageObserverTest { clearAllMocks() every { sharedPreferenceUtil.prefStorage } returns "a" every { fileSearch.scan(scanningProgressListener) } returns files - every { downloadDao.downloads() } returns downloads + every { downloadRoomDao.downloads() } returns downloads every { zimFileReader.jniKiwixReader } returns mockk() every { runBlocking { readerFactory.create(file) } } returns zimFileReader - storageObserver = StorageObserver(downloadDao, fileSearch, readerFactory, libkiwixBookmarks) + storageObserver = StorageObserver(downloadRoomDao, fileSearch, readerFactory, libkiwixBookmarks) } @Test diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt index 3aa3631792..760f2a412e 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModel import io.reactivex.disposables.CompositeDisposable import io.reactivex.processors.PublishProcessor import org.kiwix.kiwixmobile.core.base.SideEffect +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Failed @@ -39,7 +40,7 @@ import org.kiwix.kiwixmobile.custom.download.effects.SetPreferredStorageWithMost import javax.inject.Inject class CustomDownloadViewModel @Inject constructor( - downloadDao: FetchDownloadDao, + downloadRoomDao: DownloadRoomDao, setPreferredStorageWithMostSpace: SetPreferredStorageWithMostSpace, private val downloadCustom: DownloadCustom, private val navigateToCustomReader: NavigateToCustomReader @@ -55,7 +56,7 @@ class CustomDownloadViewModel @Inject constructor( init { compositeDisposable.addAll( reducer(), - downloadsAsActions(downloadDao) + downloadsAsActions(downloadRoomDao) ) } @@ -65,8 +66,8 @@ class CustomDownloadViewModel @Inject constructor( .distinctUntilChanged() .subscribe(state::postValue, Throwable::printStackTrace) - private fun downloadsAsActions(downloadDao: FetchDownloadDao) = - downloadDao.downloads() + private fun downloadsAsActions(downloadRoomDao: DownloadRoomDao) = + downloadRoomDao.downloads() .map { it.map(::DownloadItem) } .subscribe( { actions.offer(DatabaseEmission(it)) }, diff --git a/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt b/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt index d29e1e1f81..489aea6600 100644 --- a/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt +++ b/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Failed @@ -47,7 +47,7 @@ import org.kiwix.sharedFunctions.downloadItem @ExtendWith(InstantExecutorExtension::class) internal class CustomDownloadViewModelTest { - private val fetchDownloadDao: FetchDownloadDao = mockk() + private val downloadRoomDao: DownloadRoomDao = mockk() private val setPreferredStorageWithMostSpace: SetPreferredStorageWithMostSpace = mockk() private val downloadCustom: DownloadCustom = mockk() private val navigateToCustomReader: NavigateToCustomReader = mockk() @@ -58,9 +58,9 @@ internal class CustomDownloadViewModelTest { @BeforeEach internal fun setUp() { clearAllMocks() - every { fetchDownloadDao.downloads() } returns downloads + every { downloadRoomDao.downloads() } returns downloads customDownloadViewModel = CustomDownloadViewModel( - fetchDownloadDao, + downloadRoomDao, setPreferredStorageWithMostSpace, downloadCustom, navigateToCustomReader From d67262224bcc225d9486a31f9def582eff84f165 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 23 Jul 2024 19:23:53 +0530 Subject: [PATCH 04/41] Showing progress, eta time on download screen. --- .../downloadManager/DownloadManagerMonitor.kt | 330 ++++++++++++------ .../core/downloader/model/DownloadModel.kt | 4 +- 2 files changed, 233 insertions(+), 101 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 14e8389887..b3ce5a1bc8 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -18,132 +18,69 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager +import android.annotation.SuppressLint import android.app.DownloadManager import android.content.Intent +import android.database.Cursor import android.util.Log +import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import java.util.concurrent.TimeUnit import javax.inject.Inject class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, private val downloadRoomDao: DownloadRoomDao -) : - DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { +) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { + private val updater = PublishSubject.create<() -> Unit>() private val lock = Any() + private val downloadInfoMap = mutableMapOf() + + init { + startMonitoringDownloads() + setupUpdater() + } override fun downloadInformation(intent: Intent) { synchronized(lock) { intent.extras?.let { val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) if (downloadedFileId != -1L) { - val query = DownloadManager.Query().setFilterById(downloadedFileId) - val cursor = downloadManager.query(query) - if (cursor.moveToFirst()) { - val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) - val status = cursor.getInt(columnIndex) - val columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON) - val reason = cursor.getInt(columnReason) - - when (status) { - DownloadManager.STATUS_FAILED -> { - var error = Error.NONE - when (reason) { - DownloadManager.ERROR_CANNOT_RESUME -> error = Error.ERROR_CANNOT_RESUME - DownloadManager.ERROR_DEVICE_NOT_FOUND -> error = Error.ERROR_DEVICE_NOT_FOUND - DownloadManager.ERROR_FILE_ALREADY_EXISTS -> error = - Error.ERROR_FILE_ALREADY_EXISTS - - DownloadManager.ERROR_FILE_ERROR -> error = Error.ERROR_FILE_ERROR - DownloadManager.ERROR_HTTP_DATA_ERROR -> error = Error.ERROR_HTTP_DATA_ERROR - DownloadManager.ERROR_INSUFFICIENT_SPACE -> error = Error.ERROR_INSUFFICIENT_SPACE - - DownloadManager.ERROR_TOO_MANY_REDIRECTS -> error = Error.ERROR_TOO_MANY_REDIRECTS - - DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> error = - Error.ERROR_UNHANDLED_HTTP_CODE - - DownloadManager.ERROR_UNKNOWN -> error = Error.UNKNOWN - } - updater.onNext { - updateDownloadStatus( - downloadedFileId, - Status.FAILED, - error - ) - } - } - - DownloadManager.STATUS_PAUSED -> { - updater.onNext { - updateDownloadStatus( - downloadedFileId, - Status.PAUSED, - Error.NONE - ) - } - } - - DownloadManager.STATUS_PENDING -> { - updater.onNext { - updateDownloadStatus( - downloadedFileId, - Status.QUEUED, - Error.NONE - ) - } - } - - DownloadManager.STATUS_RUNNING -> { - updater.onNext { - updateDownloadStatus( - downloadedFileId, - Status.DOWNLOADING, - Error.NONE - ) - } - } - - DownloadManager.STATUS_SUCCESSFUL -> { - updater.onNext { - updateDownloadStatus( - downloadedFileId, - Status.COMPLETED, - Error.NONE - ) - } - downloadManager.remove(downloadedFileId) - } - } - } else { - // the download is cancelled - updater.onNext { - updateDownloadStatus(downloadedFileId, Status.CANCELLED, Error.CANCELLED) - downloadRoomDao.delete(downloadedFileId) - } - } - cursor.close() - Log.e("HELLO", "downloadInformation: $downloadedFileId") + queryDownloadStatus(downloadedFileId) } } } } - private fun updateDownloadStatus(downloadFileId: Long, status: Status, error: Error) { - downloadRoomDao.getEntityForDownloadId(downloadFileId)?.let { downloadEntity -> - val downloadModel = DownloadModel(downloadEntity).apply { - state = status - this.error = error - } - updater.onNext { downloadRoomDao.update(downloadModel) } - } + private fun startMonitoringDownloads() { + // we have to disable this when no downloads is ongoing + // and should re-enable when download started. + Observable.interval(0, 5, TimeUnit.SECONDS) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe( + { + try { + if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { + checkDownloads() + } + } catch (ignore: Exception) { + Log.i( + "DOWNLOAD_MONITOR", + "Couldn't get the downloads update. Original exception = $ignore" + ) + } + }, + Throwable::printStackTrace + ) } - init { + private fun setupUpdater() { updater.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe( { synchronized(lock) { it.invoke() } @@ -152,7 +89,202 @@ class DownloadManagerMonitor @Inject constructor( ) } + @SuppressLint("Range") + private fun checkDownloads() { + synchronized(lock) { + val query = DownloadManager.Query().setFilterByStatus( + DownloadManager.STATUS_RUNNING or + DownloadManager.STATUS_PAUSED or + DownloadManager.STATUS_PENDING + ) + downloadManager.query(query).use { cursor -> + if (cursor.moveToFirst()) { + do { + val downloadedFileId = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)) + queryDownloadStatus(downloadedFileId) + } while (cursor.moveToNext()) + } + } + } + } + + @SuppressLint("Range") + private fun queryDownloadStatus(downloadedFileId: Long) { + synchronized(lock) { + downloadManager.query(DownloadManager.Query().setFilterById(downloadedFileId)).use { cursor -> + if (cursor.moveToFirst()) { + handleDownloadStatus(cursor, downloadedFileId) + } else { + handleCancelledDownload(downloadedFileId) + } + } + } + } + + @SuppressLint("Range") + private fun handleDownloadStatus(cursor: Cursor, downloadedFileId: Long) { + val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) + val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)) + val bytesDownloaded = + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) + val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) + val progress = calculateProgress(bytesDownloaded, totalBytes) + + Log.e( + "HELLO", + "downloadInformation: $downloadedFileId\n totalBytes= $totalBytes \n bytesDownloaded = $bytesDownloaded \n progress = $progress" + ) + + val etaInMilliSeconds = calculateETA(downloadedFileId, bytesDownloaded, totalBytes) + + when (status) { + DownloadManager.STATUS_FAILED -> handleFailedDownload( + downloadedFileId, + reason, + progress, + etaInMilliSeconds + ) + + DownloadManager.STATUS_PAUSED -> handlePausedDownload( + downloadedFileId, + progress, + etaInMilliSeconds + ) + + DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadedFileId, etaInMilliSeconds) + DownloadManager.STATUS_RUNNING -> handleRunningDownload( + downloadedFileId, + progress, + etaInMilliSeconds + ) + + DownloadManager.STATUS_SUCCESSFUL -> handleSuccessfulDownload( + downloadedFileId, + progress, + etaInMilliSeconds + ) + } + } + + private fun handleCancelledDownload(downloadedFileId: Long) { + updater.onNext { + updateDownloadStatus(downloadedFileId, Status.CANCELLED, Error.CANCELLED) + downloadRoomDao.delete(downloadedFileId) + } + } + + private fun handleFailedDownload( + downloadedFileId: Long, + reason: Int, + progress: Int, + etaInMilliSeconds: Long + ) { + val error = mapDownloadError(reason) + updateDownloadStatus(downloadedFileId, Status.FAILED, error, progress, etaInMilliSeconds) + } + + private fun handlePausedDownload(downloadedFileId: Long, progress: Int, etaInMilliSeconds: Long) { + updateDownloadStatus(downloadedFileId, Status.PAUSED, Error.NONE, progress, etaInMilliSeconds) + } + + private fun handlePendingDownload(downloadedFileId: Long, etaInMilliSeconds: Long) { + updateDownloadStatus( + downloadedFileId, + Status.QUEUED, + Error.NONE, + etaInMilliSeconds = etaInMilliSeconds + ) + } + + private fun handleRunningDownload( + downloadedFileId: Long, + progress: Int, + etaInMilliSeconds: Long + ) { + updateDownloadStatus( + downloadedFileId, + Status.DOWNLOADING, + Error.NONE, + progress, + etaInMilliSeconds + ) + } + + private fun handleSuccessfulDownload( + downloadedFileId: Long, + progress: Int, + etaInMilliSeconds: Long + ) { + updateDownloadStatus( + downloadedFileId, + Status.COMPLETED, + Error.NONE, + progress, + etaInMilliSeconds + ) + downloadManager.remove(downloadedFileId) + } + + private fun calculateProgress(bytesDownloaded: Int, totalBytes: Int): Int = + if (totalBytes > 0) ((bytesDownloaded / totalBytes.toDouble()) * 100).toInt() else 0 + + private fun calculateETA(downloadedFileId: Long, bytesDownloaded: Int, totalBytes: Int): Long { + val currentTime = System.currentTimeMillis() + val downloadInfo = downloadInfoMap.getOrPut(downloadedFileId) { + DownloadInfo(startTime = currentTime, initialBytesDownloaded = bytesDownloaded) + } + + val elapsedTime = currentTime - downloadInfo.startTime + val downloadSpeed = + if (elapsedTime > 0) (bytesDownloaded - downloadInfo.initialBytesDownloaded) / (elapsedTime / 1000.0) else 0.0 + return if (downloadSpeed > 0) ((totalBytes - bytesDownloaded) / downloadSpeed).toLong() * 1000 else 0L + } + + private fun mapDownloadError(reason: Int): Error { + return when (reason) { + DownloadManager.ERROR_CANNOT_RESUME -> Error.ERROR_CANNOT_RESUME + DownloadManager.ERROR_DEVICE_NOT_FOUND -> Error.ERROR_DEVICE_NOT_FOUND + DownloadManager.ERROR_FILE_ALREADY_EXISTS -> Error.ERROR_FILE_ALREADY_EXISTS + DownloadManager.ERROR_FILE_ERROR -> Error.ERROR_FILE_ERROR + DownloadManager.ERROR_HTTP_DATA_ERROR -> Error.ERROR_HTTP_DATA_ERROR + DownloadManager.ERROR_INSUFFICIENT_SPACE -> Error.ERROR_INSUFFICIENT_SPACE + DownloadManager.ERROR_TOO_MANY_REDIRECTS -> Error.ERROR_TOO_MANY_REDIRECTS + DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> Error.ERROR_UNHANDLED_HTTP_CODE + DownloadManager.ERROR_UNKNOWN -> Error.UNKNOWN + else -> Error.UNKNOWN + } + } + + private fun updateDownloadStatus( + downloadFileId: Long, + status: Status, + error: Error, + progress: Int = -1, + etaInMilliSeconds: Long = -1L + ) { + synchronized(lock) { + downloadRoomDao.getEntityForDownloadId(downloadFileId)?.let { downloadEntity -> + val downloadModel = DownloadModel(downloadEntity).apply { + state = status + this.error = error + if (progress > 0) { + this.progress = progress + } + if (etaInMilliSeconds != -1L) { + this.etaInMilliSeconds = etaInMilliSeconds + } + } + updater.onNext { downloadRoomDao.update(downloadModel) } + } + } + } + override fun init() { - // empty method to so class does not get reported unused} + // empty method to so class does not get reported unused } } + +data class DownloadInfo( + var startTime: Long, + var initialBytesDownloaded: Int +) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt index 73416b639d..ff956bb7dc 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt @@ -27,12 +27,12 @@ data class DownloadModel( val databaseId: Long, val downloadId: Long, val file: String?, - val etaInMilliSeconds: Long, + var etaInMilliSeconds: Long, val bytesDownloaded: Long, val totalSizeOfDownload: Long, var state: Status, var error: Error, - val progress: Int, + var progress: Int, val book: Book ) { val bytesRemaining: Long by lazy { totalSizeOfDownload - bytesDownloaded } From 7e79f288a5f6a262d1c4c04e1f229880eda11055 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Wed, 24 Jul 2024 18:29:05 +0530 Subject: [PATCH 05/41] Implemented the pause/resume functionality for downloads. --- .../kiwixmobile/core/dao/DownloadRoomDao.kt | 1 + .../core/dao/entities/DownloadRoomEntity.kt | 6 +- .../core/di/modules/DownloaderModule.kt | 13 +- .../DownloadManagerBroadcastReceiver.kt | 5 +- .../downloadManager/DownloadManagerMonitor.kt | 141 ++++++++++++++---- .../DownloadManagerRequester.kt | 20 ++- .../DownloadNotificationActionsReceiver.kt | 35 +++++ .../core/downloader/model/DownloadModel.kt | 6 +- 8 files changed, 187 insertions(+), 40 deletions(-) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt index 59212fe4dd..aee01654cb 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt @@ -105,6 +105,7 @@ abstract class DownloadRoomDao { val downloadRequest = DownloadRequest(url, book.title) saveDownload( DownloadRoomEntity( + url, downloadRequester.enqueue(downloadRequest), book = book, file = downloadRequest.getDestinationFile(sharedPreferenceUtil).path diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt index 37135dd722..1d1256eb07 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt @@ -37,7 +37,7 @@ data class DownloadRoomEntity( val bytesDownloaded: Long = -1L, val totalSizeOfDownload: Long = -1L, @Convert(converter = StatusConverter::class, dbType = Int::class) - val status: Status = Status.NONE, + var status: Status = Status.NONE, @Convert(converter = ErrorConverter::class, dbType = Int::class) val error: Error = Error.NONE, val progress: Int = -1, @@ -56,7 +56,7 @@ data class DownloadRoomEntity( val favIcon: String, val tags: String? = null ) { - constructor(downloadId: Long, book: Book, file: String?) : this( + constructor(downloadUrl: String, downloadId: Long, book: Book, file: String?) : this( file = file, downloadId = downloadId, bookId = book.id, @@ -66,7 +66,7 @@ data class DownloadRoomEntity( creator = book.creator, publisher = book.publisher, date = book.date, - url = book.url, + url = downloadUrl, articleCount = book.articleCount, mediaCount = book.mediaCount, size = book.size, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 5fadc41737..735f6cbfde 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -38,6 +38,7 @@ import org.kiwix.kiwixmobile.core.downloader.DownloaderImpl import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerRequester +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsReceiver import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadNotificationManager import org.kiwix.kiwixmobile.core.utils.CONNECT_TIME_OUT import org.kiwix.kiwixmobile.core.utils.READ_TIME_OUT @@ -105,11 +106,13 @@ object DownloaderModule { fun providesDownloadRequester( downloadManager: DownloadManager, sharedPreferenceUtil: SharedPreferenceUtil, - downloadRoomDao: DownloadRoomDao + downloadRoomDao: DownloadRoomDao, + downloadManagerMonitor: DownloadManagerMonitor ): DownloadRequester = DownloadManagerRequester( downloadManager, sharedPreferenceUtil, - downloadRoomDao + downloadRoomDao, + downloadManagerMonitor ) @Provides @@ -121,4 +124,10 @@ object DownloaderModule { @Singleton fun providesDownloadManagerBroadcastReceiver(callback: DownloadManagerBroadcastReceiver.Callback) : DownloadManagerBroadcastReceiver = DownloadManagerBroadcastReceiver(callback) + + @Provides + @Singleton + fun providesDownloadNotificationActionsReceiver(downloadManagerMonitor: DownloadManagerMonitor) + : DownloadNotificationActionsReceiver = + DownloadNotificationActionsReceiver(downloadManagerMonitor) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt index 8cfe917c19..737ccbf676 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerBroadcastReceiver.kt @@ -21,7 +21,6 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.app.DownloadManager.ACTION_DOWNLOAD_COMPLETE import android.content.Context import android.content.Intent -import android.util.Log import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver import javax.inject.Inject @@ -31,10 +30,10 @@ class DownloadManagerBroadcastReceiver @Inject constructor(private val callback: override val action: String = ACTION_DOWNLOAD_COMPLETE override fun onIntentWithActionReceived(context: Context, intent: Intent) { - callback.downloadInformation(intent) + callback.downloadCompleteOrCancelled(intent) } interface Callback { - fun downloadInformation(intent: Intent) + fun downloadCompleteOrCancelled(intent: Intent) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index b3ce5a1bc8..9f329d38c2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -20,8 +20,11 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.annotation.SuppressLint import android.app.DownloadManager +import android.content.ContentValues +import android.content.Context import android.content.Intent import android.database.Cursor +import android.net.Uri import android.util.Log import io.reactivex.Observable import io.reactivex.schedulers.Schedulers @@ -34,7 +37,8 @@ import javax.inject.Inject class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, - private val downloadRoomDao: DownloadRoomDao + private val downloadRoomDao: DownloadRoomDao, + private val context: Context ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val updater = PublishSubject.create<() -> Unit>() @@ -46,7 +50,7 @@ class DownloadManagerMonitor @Inject constructor( setupUpdater() } - override fun downloadInformation(intent: Intent) { + override fun downloadCompleteOrCancelled(intent: Intent) { synchronized(lock) { intent.extras?.let { val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) @@ -130,11 +134,6 @@ class DownloadManagerMonitor @Inject constructor( val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) val progress = calculateProgress(bytesDownloaded, totalBytes) - Log.e( - "HELLO", - "downloadInformation: $downloadedFileId\n totalBytes= $totalBytes \n bytesDownloaded = $bytesDownloaded \n progress = $progress" - ) - val etaInMilliSeconds = calculateETA(downloadedFileId, bytesDownloaded, totalBytes) when (status) { @@ -142,20 +141,26 @@ class DownloadManagerMonitor @Inject constructor( downloadedFileId, reason, progress, - etaInMilliSeconds + etaInMilliSeconds, + bytesDownloaded, + totalBytes ) DownloadManager.STATUS_PAUSED -> handlePausedDownload( downloadedFileId, progress, - etaInMilliSeconds + etaInMilliSeconds, + bytesDownloaded, + totalBytes ) DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadedFileId, etaInMilliSeconds) DownloadManager.STATUS_RUNNING -> handleRunningDownload( downloadedFileId, progress, - etaInMilliSeconds + etaInMilliSeconds, + bytesDownloaded, + totalBytes ) DownloadManager.STATUS_SUCCESSFUL -> handleSuccessfulDownload( @@ -170,6 +175,7 @@ class DownloadManagerMonitor @Inject constructor( updater.onNext { updateDownloadStatus(downloadedFileId, Status.CANCELLED, Error.CANCELLED) downloadRoomDao.delete(downloadedFileId) + downloadInfoMap.remove(downloadedFileId) } } @@ -177,14 +183,38 @@ class DownloadManagerMonitor @Inject constructor( downloadedFileId: Long, reason: Int, progress: Int, - etaInMilliSeconds: Long + etaInMilliSeconds: Long, + bytesDownloaded: Int, + totalBytes: Int ) { val error = mapDownloadError(reason) - updateDownloadStatus(downloadedFileId, Status.FAILED, error, progress, etaInMilliSeconds) + updateDownloadStatus( + downloadedFileId, + Status.FAILED, + error, + progress, + etaInMilliSeconds, + bytesDownloaded, + totalBytes + ) } - private fun handlePausedDownload(downloadedFileId: Long, progress: Int, etaInMilliSeconds: Long) { - updateDownloadStatus(downloadedFileId, Status.PAUSED, Error.NONE, progress, etaInMilliSeconds) + private fun handlePausedDownload( + downloadedFileId: Long, + progress: Int, + etaInMilliSeconds: Long, + bytesDownloaded: Int, + totalSizeOfDownload: Int + ) { + updateDownloadStatus( + downloadedFileId, + Status.PAUSED, + Error.NONE, + progress, + etaInMilliSeconds, + bytesDownloaded, + totalSizeOfDownload + ) } private fun handlePendingDownload(downloadedFileId: Long, etaInMilliSeconds: Long) { @@ -199,14 +229,18 @@ class DownloadManagerMonitor @Inject constructor( private fun handleRunningDownload( downloadedFileId: Long, progress: Int, - etaInMilliSeconds: Long + etaInMilliSeconds: Long, + bytesDownloaded: Int, + totalSizeOfDownload: Int ) { updateDownloadStatus( downloadedFileId, Status.DOWNLOADING, Error.NONE, progress, - etaInMilliSeconds + etaInMilliSeconds, + bytesDownloaded, + totalSizeOfDownload ) } @@ -222,7 +256,7 @@ class DownloadManagerMonitor @Inject constructor( progress, etaInMilliSeconds ) - downloadManager.remove(downloadedFileId) + downloadInfoMap.remove(downloadedFileId) } private fun calculateProgress(bytesDownloaded: Int, totalBytes: Int): Int = @@ -260,25 +294,76 @@ class DownloadManagerMonitor @Inject constructor( status: Status, error: Error, progress: Int = -1, - etaInMilliSeconds: Long = -1L + etaInMilliSeconds: Long = -1L, + bytesDownloaded: Int = -1, + totalSizeOfDownload: Int = -1 ) { synchronized(lock) { - downloadRoomDao.getEntityForDownloadId(downloadFileId)?.let { downloadEntity -> - val downloadModel = DownloadModel(downloadEntity).apply { - state = status - this.error = error - if (progress > 0) { - this.progress = progress - } - if (etaInMilliSeconds != -1L) { - this.etaInMilliSeconds = etaInMilliSeconds + updater.onNext { + downloadRoomDao.getEntityForDownloadId(downloadFileId)?.let { downloadEntity -> + val downloadModel = DownloadModel(downloadEntity).apply { + state = status + this.error = error + if (progress > 0) { + this.progress = progress + } + if (etaInMilliSeconds != -1L) { + this.etaInMilliSeconds = etaInMilliSeconds + } + if (bytesDownloaded != -1) { + this.bytesDownloaded = bytesDownloaded.toLong() + } + if (totalSizeOfDownload != -1) { + this.totalSizeOfDownload = totalSizeOfDownload.toLong() + } } + downloadRoomDao.update(downloadModel) } - updater.onNext { downloadRoomDao.update(downloadModel) } } } } + fun pauseDownload(downloadedFileId: Long) { + synchronized(lock) { + if (pauseResumeDownloadInDownloadManagerContentResolver(downloadedFileId, 1)) { + updateDownloadStatus(downloadedFileId, Status.PAUSED, Error.NONE) + } + } + } + + fun resumeDownload(downloadedFileId: Long) { + synchronized(lock) { + updater.onNext { + if (pauseResumeDownloadInDownloadManagerContentResolver(downloadedFileId, 0)) { + updateDownloadStatus(downloadedFileId, Status.QUEUED, Error.NONE) + } + } + } + } + + private fun pauseResumeDownloadInDownloadManagerContentResolver( + downloadId: Long, + control: Int + ): Boolean { + return try { + // Update the status to paused in the database + val pauseDownload = ContentValues() + pauseDownload.put("control", control) + val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) + context.contentResolver + .update( + Uri.parse(downloadEntity?.file), + pauseDownload, + "title=?", + arrayOf(downloadEntity?.title) + ) + true + } catch (ignore: Exception) { + Log.e("DOWNLOAD_MONITOR", "Couldn't pause the download. Original exception = $ignore") + false + } + } + override fun init() { // empty method to so class does not get reported unused } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 62f6d02bc2..f912eca1fc 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -33,7 +33,8 @@ import javax.inject.Inject class DownloadManagerRequester @Inject constructor( private val downloadManager: DownloadManager, private val sharedPreferenceUtil: SharedPreferenceUtil, - private val downloadRoomDao: DownloadRoomDao + private val downloadRoomDao: DownloadRoomDao, + private val downloadManagerMonitor: DownloadManagerMonitor ) : DownloadRequester { override fun enqueue(downloadRequest: DownloadRequest): Long = downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) @@ -47,9 +48,26 @@ class DownloadManagerRequester @Inject constructor( } override fun retryDownload(downloadId: Long) { + // Retry the download by enqueuing it again with the same request + // CoroutineScope(Dispatchers.IO).launch { + // val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) + // downloadEntity?.let { + // val downloadRequest = DownloadRequest( + // uri = Uri.parse(it.uri), + // notificationTitle = it.notificationTitle, + // destinationFile = it.destinationFile + // ) + // enqueue(downloadRequest) + // } + // } } override fun pauseResumeDownload(downloadId: Long, isPause: Boolean) { + if (isPause) { + downloadManagerMonitor.pauseDownload(downloadId) + } else { + downloadManagerMonitor.resumeDownload(downloadId) + } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt new file mode 100644 index 0000000000..c1107eea47 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt @@ -0,0 +1,35 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +import android.content.Context +import android.content.Intent +import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver +import javax.inject.Inject + +const val DOWNLOAD_NOTIFICATION_ACTION = "download_notification_action" + +class DownloadNotificationActionsReceiver @Inject constructor( + private val downloadManagerMonitor: DownloadManagerMonitor +) : BaseBroadcastReceiver() { + + override val action: String = DOWNLOAD_NOTIFICATION_ACTION + override fun onIntentWithActionReceived(context: Context, intent: Intent) { + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt index ff956bb7dc..e76caf8847 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadModel.kt @@ -25,11 +25,11 @@ import org.kiwix.kiwixmobile.core.utils.StorageUtils data class DownloadModel( val databaseId: Long, - val downloadId: Long, + var downloadId: Long, val file: String?, var etaInMilliSeconds: Long, - val bytesDownloaded: Long, - val totalSizeOfDownload: Long, + var bytesDownloaded: Long, + var totalSizeOfDownload: Long, var state: Status, var error: Error, var progress: Int, From d394bb607863157ca46e81274f5c2b8ffdb6be79 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 25 Jul 2024 11:48:33 +0530 Subject: [PATCH 06/41] Improved the eta time calculation. --- .../downloadManager/DownloadManagerMonitor.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 9f329d38c2..112b118f5a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -32,6 +32,7 @@ import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import java.io.File import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -269,9 +270,17 @@ class DownloadManagerMonitor @Inject constructor( } val elapsedTime = currentTime - downloadInfo.startTime - val downloadSpeed = - if (elapsedTime > 0) (bytesDownloaded - downloadInfo.initialBytesDownloaded) / (elapsedTime / 1000.0) else 0.0 - return if (downloadSpeed > 0) ((totalBytes - bytesDownloaded) / downloadSpeed).toLong() * 1000 else 0L + val downloadSpeed = if (elapsedTime > 0) { + (bytesDownloaded - downloadInfo.initialBytesDownloaded) / (elapsedTime / 1000.0) + } else { + 0.0 + } + + return if (downloadSpeed > 0) { + ((totalBytes - bytesDownloaded) / downloadSpeed).toLong() * 1000 + } else { + 0L + } } private fun mapDownloadError(reason: Int): Error { @@ -352,7 +361,7 @@ class DownloadManagerMonitor @Inject constructor( val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) context.contentResolver .update( - Uri.parse(downloadEntity?.file), + Uri.fromFile(File(downloadEntity?.file)), pauseDownload, "title=?", arrayOf(downloadEntity?.title) From ef2f50539b64dcd0deab8c87538e4d2f266bb52f Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 25 Jul 2024 12:01:48 +0530 Subject: [PATCH 07/41] Not showing the eta time when download is paused --- .../downloadManager/DownloadManagerMonitor.kt | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 112b118f5a..9b0ff0a49b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.annotation.SuppressLint import android.app.DownloadManager +import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent @@ -32,7 +33,6 @@ import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel -import java.io.File import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -150,12 +150,11 @@ class DownloadManagerMonitor @Inject constructor( DownloadManager.STATUS_PAUSED -> handlePausedDownload( downloadedFileId, progress, - etaInMilliSeconds, bytesDownloaded, totalBytes ) - DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadedFileId, etaInMilliSeconds) + DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadedFileId) DownloadManager.STATUS_RUNNING -> handleRunningDownload( downloadedFileId, progress, @@ -203,27 +202,24 @@ class DownloadManagerMonitor @Inject constructor( private fun handlePausedDownload( downloadedFileId: Long, progress: Int, - etaInMilliSeconds: Long, bytesDownloaded: Int, totalSizeOfDownload: Int ) { updateDownloadStatus( - downloadedFileId, - Status.PAUSED, - Error.NONE, - progress, - etaInMilliSeconds, - bytesDownloaded, - totalSizeOfDownload + downloadFileId = downloadedFileId, + status = Status.PAUSED, + error = Error.NONE, + progress = progress, + bytesDownloaded = bytesDownloaded, + totalSizeOfDownload = totalSizeOfDownload ) } - private fun handlePendingDownload(downloadedFileId: Long, etaInMilliSeconds: Long) { + private fun handlePendingDownload(downloadedFileId: Long) { updateDownloadStatus( downloadedFileId, Status.QUEUED, - Error.NONE, - etaInMilliSeconds = etaInMilliSeconds + Error.NONE ) } @@ -316,9 +312,7 @@ class DownloadManagerMonitor @Inject constructor( if (progress > 0) { this.progress = progress } - if (etaInMilliSeconds != -1L) { - this.etaInMilliSeconds = etaInMilliSeconds - } + this.etaInMilliSeconds = etaInMilliSeconds if (bytesDownloaded != -1) { this.bytesDownloaded = bytesDownloaded.toLong() } @@ -356,19 +350,21 @@ class DownloadManagerMonitor @Inject constructor( ): Boolean { return try { // Update the status to paused in the database - val pauseDownload = ContentValues() - pauseDownload.put("control", control) + val contentValues = ContentValues() + contentValues.put("control", control) + val uri = + ContentUris.withAppendedId(Uri.parse("content://downloads/my_downloads"), downloadId) val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) context.contentResolver .update( - Uri.fromFile(File(downloadEntity?.file)), - pauseDownload, + uri, + contentValues, "title=?", arrayOf(downloadEntity?.title) ) true } catch (ignore: Exception) { - Log.e("DOWNLOAD_MONITOR", "Couldn't pause the download. Original exception = $ignore") + Log.e("DOWNLOAD_MONITOR", "Couldn't pause/resume the download. Original exception = $ignore") false } } From ce275254b9539db3f9265a656dca48664b4ce912 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 25 Jul 2024 18:12:29 +0530 Subject: [PATCH 08/41] Added custom notification for showing the downloading progress with pause/resume functionality --- .../library/OnlineLibraryFragment.kt | 11 +- .../core/di/components/CoreComponent.kt | 2 + .../core/di/modules/CoreServiceModule.kt | 7 - .../core/di/modules/DownloaderModule.kt | 17 +- .../downloadManager/DownloadManagerMonitor.kt | 118 ++++++---- .../DownloadManagerRequester.kt | 5 +- ...adNotificationActionsBroadcastReceiver.kt} | 14 +- .../DownloadNotificationManager.kt | 208 ++++++++++++++++++ .../DownloadNotificationModel.kt | 44 ++++ .../kiwixmobile/core/main/CoreMainActivity.kt | 7 +- .../kiwix/kiwixmobile/core/utils/Constants.kt | 3 + 11 files changed, 376 insertions(+), 60 deletions(-) rename core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/{DownloadNotificationActionsReceiver.kt => DownloadNotificationActionsBroadcastReceiver.kt} (58%) create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt create mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index e63ce12fa0..b976d6f05b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -29,6 +29,7 @@ import android.net.ConnectivityManager import android.os.Build import android.os.Bundle import android.provider.Settings +import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -136,6 +137,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { }, { context?.let { context -> + Log.e("STATUS", ": ${it.downloadState.toReadableState(context)}") downloader.pauseResumeDownload( it.downloadId, it.downloadState.toReadableState(context) == getString(R.string.paused_state) @@ -230,7 +232,14 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { date = "2024-06-24" faviconMimeType = "image/png" } - // downloader.download(libraryBookEntity) + onLibraryItemsChange( + arrayListOf( + LibraryListItem.BookItem( + libraryBookEntity, + Fat32Checker.FileSystemState.CanWrite4GbFile + ) + ) + ) } private fun setupMenu() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index 9a1b764903..da695dadf1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -18,6 +18,7 @@ package org.kiwix.kiwixmobile.core.di.components import android.app.Application +import android.app.DownloadManager import android.app.NotificationManager import android.content.Context import android.net.ConnectivityManager @@ -114,6 +115,7 @@ interface CoreComponent { fun context(): Context fun downloader(): Downloader fun notificationManager(): NotificationManager + fun downloadManager(): DownloadManager fun searchResultGenerator(): SearchResultGenerator fun mutex(): Mutex diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt index 2d605cf701..aae94c461b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt @@ -77,11 +77,4 @@ class CoreServiceModule { @Provides @CoreServiceScope fun providesGenerateQr(): GenerateQR = GenerateQR() - fun providesDownloadManagerBroadcastReceiver(callback: DownloadManagerBroadcastReceiver.Callback) - : DownloadManagerBroadcastReceiver = DownloadManagerBroadcastReceiver(callback) - - @Provides - @CoreServiceScope - fun providesDownloadInformationCallback(downloadManagerMonitor: DownloadManagerMonitor) - : DownloadManagerBroadcastReceiver.Callback = downloadManagerMonitor } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 735f6cbfde..5b16df3965 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -18,6 +18,7 @@ package org.kiwix.kiwixmobile.core.di.modules import android.app.DownloadManager +import android.app.NotificationManager import android.content.Context import com.tonyodev.fetch2.Fetch import com.tonyodev.fetch2.Fetch.Impl @@ -38,7 +39,8 @@ import org.kiwix.kiwixmobile.core.downloader.DownloaderImpl import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerRequester -import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsBroadcastReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadNotificationManager import org.kiwix.kiwixmobile.core.utils.CONNECT_TIME_OUT import org.kiwix.kiwixmobile.core.utils.READ_TIME_OUT @@ -127,7 +129,14 @@ object DownloaderModule { @Provides @Singleton - fun providesDownloadNotificationActionsReceiver(downloadManagerMonitor: DownloadManagerMonitor) - : DownloadNotificationActionsReceiver = - DownloadNotificationActionsReceiver(downloadManagerMonitor) + fun providesDownloadNotificationActionsBroadcastReceiver(downloadManagerMonitor: DownloadManagerMonitor) + : DownloadNotificationActionsBroadcastReceiver = + DownloadNotificationActionsBroadcastReceiver(downloadManagerMonitor) + + @Provides + @Singleton + fun providesDownloadNotificationManager( + context: Context, + notificationManager: NotificationManager + ): DownloadNotificationManager = DownloadNotificationManager(context, notificationManager) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 9b0ff0a49b..cc923cf663 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -20,6 +20,8 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.annotation.SuppressLint import android.app.DownloadManager +import android.app.DownloadManager.STATUS_PAUSED +import android.app.DownloadManager.STATUS_RUNNING import android.content.ContentUris import android.content.ContentValues import android.content.Context @@ -33,13 +35,15 @@ import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import java.util.concurrent.TimeUnit import javax.inject.Inject class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, private val downloadRoomDao: DownloadRoomDao, - private val context: Context + private val context: Context, + private val downloadNotificationManager: DownloadNotificationManager ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val updater = PublishSubject.create<() -> Unit>() @@ -54,9 +58,9 @@ class DownloadManagerMonitor @Inject constructor( override fun downloadCompleteOrCancelled(intent: Intent) { synchronized(lock) { intent.extras?.let { - val downloadedFileId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) - if (downloadedFileId != -1L) { - queryDownloadStatus(downloadedFileId) + val downloadId = it.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) + if (downloadId != -1L) { + queryDownloadStatus(downloadId) } } } @@ -105,8 +109,8 @@ class DownloadManagerMonitor @Inject constructor( downloadManager.query(query).use { cursor -> if (cursor.moveToFirst()) { do { - val downloadedFileId = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)) - queryDownloadStatus(downloadedFileId) + val downloadId = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)) + queryDownloadStatus(downloadId) } while (cursor.moveToNext()) } } @@ -114,20 +118,20 @@ class DownloadManagerMonitor @Inject constructor( } @SuppressLint("Range") - private fun queryDownloadStatus(downloadedFileId: Long) { + private fun queryDownloadStatus(downloadId: Long) { synchronized(lock) { - downloadManager.query(DownloadManager.Query().setFilterById(downloadedFileId)).use { cursor -> + downloadManager.query(DownloadManager.Query().setFilterById(downloadId)).use { cursor -> if (cursor.moveToFirst()) { - handleDownloadStatus(cursor, downloadedFileId) + handleDownloadStatus(cursor, downloadId) } else { - handleCancelledDownload(downloadedFileId) + handleCancelledDownload(downloadId) } } } } @SuppressLint("Range") - private fun handleDownloadStatus(cursor: Cursor, downloadedFileId: Long) { + private fun handleDownloadStatus(cursor: Cursor, downloadId: Long) { val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)) val bytesDownloaded = @@ -135,11 +139,11 @@ class DownloadManagerMonitor @Inject constructor( val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) val progress = calculateProgress(bytesDownloaded, totalBytes) - val etaInMilliSeconds = calculateETA(downloadedFileId, bytesDownloaded, totalBytes) + val etaInMilliSeconds = calculateETA(downloadId, bytesDownloaded, totalBytes) when (status) { DownloadManager.STATUS_FAILED -> handleFailedDownload( - downloadedFileId, + downloadId, reason, progress, etaInMilliSeconds, @@ -148,15 +152,15 @@ class DownloadManagerMonitor @Inject constructor( ) DownloadManager.STATUS_PAUSED -> handlePausedDownload( - downloadedFileId, + downloadId, progress, bytesDownloaded, totalBytes ) - DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadedFileId) + DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadId) DownloadManager.STATUS_RUNNING -> handleRunningDownload( - downloadedFileId, + downloadId, progress, etaInMilliSeconds, bytesDownloaded, @@ -164,23 +168,23 @@ class DownloadManagerMonitor @Inject constructor( ) DownloadManager.STATUS_SUCCESSFUL -> handleSuccessfulDownload( - downloadedFileId, + downloadId, progress, etaInMilliSeconds ) } } - private fun handleCancelledDownload(downloadedFileId: Long) { + private fun handleCancelledDownload(downloadId: Long) { updater.onNext { - updateDownloadStatus(downloadedFileId, Status.CANCELLED, Error.CANCELLED) - downloadRoomDao.delete(downloadedFileId) - downloadInfoMap.remove(downloadedFileId) + updateDownloadStatus(downloadId, Status.CANCELLED, Error.CANCELLED) + downloadRoomDao.delete(downloadId) + downloadInfoMap.remove(downloadId) } } private fun handleFailedDownload( - downloadedFileId: Long, + downloadId: Long, reason: Int, progress: Int, etaInMilliSeconds: Long, @@ -189,7 +193,7 @@ class DownloadManagerMonitor @Inject constructor( ) { val error = mapDownloadError(reason) updateDownloadStatus( - downloadedFileId, + downloadId, Status.FAILED, error, progress, @@ -200,13 +204,13 @@ class DownloadManagerMonitor @Inject constructor( } private fun handlePausedDownload( - downloadedFileId: Long, + downloadId: Long, progress: Int, bytesDownloaded: Int, totalSizeOfDownload: Int ) { updateDownloadStatus( - downloadFileId = downloadedFileId, + downloadId = downloadId, status = Status.PAUSED, error = Error.NONE, progress = progress, @@ -215,23 +219,23 @@ class DownloadManagerMonitor @Inject constructor( ) } - private fun handlePendingDownload(downloadedFileId: Long) { + private fun handlePendingDownload(downloadId: Long) { updateDownloadStatus( - downloadedFileId, + downloadId, Status.QUEUED, Error.NONE ) } private fun handleRunningDownload( - downloadedFileId: Long, + downloadId: Long, progress: Int, etaInMilliSeconds: Long, bytesDownloaded: Int, totalSizeOfDownload: Int ) { updateDownloadStatus( - downloadedFileId, + downloadId, Status.DOWNLOADING, Error.NONE, progress, @@ -242,18 +246,18 @@ class DownloadManagerMonitor @Inject constructor( } private fun handleSuccessfulDownload( - downloadedFileId: Long, + downloadId: Long, progress: Int, etaInMilliSeconds: Long ) { updateDownloadStatus( - downloadedFileId, + downloadId, Status.COMPLETED, Error.NONE, progress, etaInMilliSeconds ) - downloadInfoMap.remove(downloadedFileId) + downloadInfoMap.remove(downloadId) } private fun calculateProgress(bytesDownloaded: Int, totalBytes: Int): Int = @@ -295,7 +299,7 @@ class DownloadManagerMonitor @Inject constructor( } private fun updateDownloadStatus( - downloadFileId: Long, + downloadId: Long, status: Status, error: Error, progress: Int = -1, @@ -305,7 +309,7 @@ class DownloadManagerMonitor @Inject constructor( ) { synchronized(lock) { updater.onNext { - downloadRoomDao.getEntityForDownloadId(downloadFileId)?.let { downloadEntity -> + downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity -> val downloadModel = DownloadModel(downloadEntity).apply { state = status this.error = error @@ -321,37 +325,65 @@ class DownloadManagerMonitor @Inject constructor( } } downloadRoomDao.update(downloadModel) + downloadNotificationManager.updateNotification( + DownloadNotificationModel( + downloadId = downloadId.toInt(), + status = status, + progress = progress, + etaInMilliSeconds = etaInMilliSeconds, + title = downloadEntity.title, + description = downloadEntity.description, + error = DownloadState.from( + downloadModel.state, + downloadModel.error, + downloadModel.book.url + ).toReadableState(context).toString() + ) + ) } } } } - fun pauseDownload(downloadedFileId: Long) { + fun pauseDownload(downloadId: Long) { synchronized(lock) { - if (pauseResumeDownloadInDownloadManagerContentResolver(downloadedFileId, 1)) { - updateDownloadStatus(downloadedFileId, Status.PAUSED, Error.NONE) + updater.onNext { + if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_PAUSED)) { + updateDownloadStatus(downloadId, Status.PAUSED, Error.NONE) + } } } } - fun resumeDownload(downloadedFileId: Long) { + fun resumeDownload(downloadId: Long) { synchronized(lock) { updater.onNext { - if (pauseResumeDownloadInDownloadManagerContentResolver(downloadedFileId, 0)) { - updateDownloadStatus(downloadedFileId, Status.QUEUED, Error.NONE) + if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_RUNNING)) { + updateDownloadStatus(downloadId, Status.QUEUED, Error.NONE) } } } } + fun cancelDownload(downloadId: Long) { + synchronized(lock) { + updater.onNext { + downloadManager.remove(downloadId) + } + } + } + + @SuppressLint("Range") private fun pauseResumeDownloadInDownloadManagerContentResolver( downloadId: Long, control: Int ): Boolean { + Log.e("PAUSED", "pauseResumeDownloadInDownloadManagerContentResolver: $control") return try { - // Update the status to paused in the database - val contentValues = ContentValues() - contentValues.put("control", control) + // Update the status to paused/resumed in the database + val contentValues = ContentValues().apply { + put("control", control) + } val uri = ContentUris.withAppendedId(Uri.parse("content://downloads/my_downloads"), downloadId) val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index f912eca1fc..474d7dbdf7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -64,9 +64,9 @@ class DownloadManagerRequester @Inject constructor( override fun pauseResumeDownload(downloadId: Long, isPause: Boolean) { if (isPause) { - downloadManagerMonitor.pauseDownload(downloadId) - } else { downloadManagerMonitor.resumeDownload(downloadId) + } else { + downloadManagerMonitor.pauseDownload(downloadId) } } } @@ -82,5 +82,4 @@ private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: Share ) setTitle(notificationTitle) setAllowedOverMetered(true) - setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt similarity index 58% rename from core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt rename to core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt index c1107eea47..b8af67fe25 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsReceiver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt @@ -21,15 +21,27 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.content.Context import android.content.Intent import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.EXTRA_DOWNLOAD_ID import javax.inject.Inject const val DOWNLOAD_NOTIFICATION_ACTION = "download_notification_action" -class DownloadNotificationActionsReceiver @Inject constructor( +class DownloadNotificationActionsBroadcastReceiver @Inject constructor( private val downloadManagerMonitor: DownloadManagerMonitor ) : BaseBroadcastReceiver() { override val action: String = DOWNLOAD_NOTIFICATION_ACTION override fun onIntentWithActionReceived(context: Context, intent: Intent) { + val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1L) + if (downloadId != -1L) { + when (intent.action) { + ACTION_PAUSE -> downloadManagerMonitor.pauseDownload(downloadId) + ACTION_RESUME -> downloadManagerMonitor.resumeDownload(downloadId) + ACTION_CANCEL -> downloadManagerMonitor.cancelDownload(downloadId) + } + } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt new file mode 100644 index 0000000000..c4606a7e59 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -0,0 +1,208 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +import android.annotation.SuppressLint +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import org.kiwix.kiwixmobile.core.Intents +import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.downloader.fetch.DOWNLOAD_NOTIFICATION_TITLE +import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.core.downloader.model.Seconds +import org.kiwix.kiwixmobile.core.main.CoreMainActivity +import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER +import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET +import javax.inject.Inject + +class DownloadNotificationManager @Inject constructor( + private val context: Context, + private val notificationManager: NotificationManager +) { + private val downloadNotificationsMap = mutableMapOf() + private val downloadNotificationsBuilderMap = mutableMapOf() + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) { + notificationManager.createNotificationChannel(createChannel(CHANNEL_ID, context)) + } + } + } + + fun updateNotification( + downloadNotificationModel: DownloadNotificationModel + ) { + createNotificationChannel() + val notificationBuilder = getNotificationBuilder(downloadNotificationModel.downloadId) + val smallIcon = if (downloadNotificationModel.progress != 100) { + android.R.drawable.stat_sys_download + } else { + android.R.drawable.stat_sys_download_done + } + + notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setSmallIcon(smallIcon) + .setContentTitle(downloadNotificationModel.title) + .setContentText(getSubtitleText(context, downloadNotificationModel)) + .setOngoing(downloadNotificationModel.isOnGoingNotification) + .setGroupSummary(false) + if (downloadNotificationModel.isFailed || downloadNotificationModel.isCompleted) { + notificationBuilder.setProgress(0, 0, false) + } else { + notificationBuilder.setProgress(100, downloadNotificationModel.progress, true) + } + when { + downloadNotificationModel.isDownloading -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + .addAction( + R.drawable.fetch_notification_cancel, + context.getString(R.string.cancel), + getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) + ).addAction( + R.drawable.fetch_notification_pause, + context.getString(R.string.tts_pause), + getActionPendingIntent(ACTION_PAUSE, downloadNotificationModel.downloadId) + ) + + downloadNotificationModel.isPaused -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + .addAction( + R.drawable.fetch_notification_resume, + context.getString(R.string.tts_resume), + getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) + ) + .addAction( + R.drawable.fetch_notification_cancel, + context.getString(R.string.cancel), + getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) + ) + + downloadNotificationModel.isQueued -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + + else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) + } + notificationCustomisation(downloadNotificationModel, notificationBuilder, context) + } + + @SuppressLint("UnspecifiedImmutableFlag") + private fun notificationCustomisation( + downloadNotificationModel: DownloadNotificationModel, + notificationBuilder: NotificationCompat.Builder, + context: Context + ) { + if (downloadNotificationModel.isCompleted) { + val internal = Intents.internal(CoreMainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(DOWNLOAD_NOTIFICATION_TITLE, downloadNotificationModel.title) + } + val pendingIntent = + PendingIntent.getActivity( + context, + 0, + internal, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + notificationBuilder.setContentIntent(pendingIntent) + notificationBuilder.setAutoCancel(true) + } + } + + @SuppressLint("RestrictedApi") + private fun getNotificationBuilder(notificationId: Int): NotificationCompat.Builder { + synchronized(downloadNotificationsMap) { + val notificationBuilder = downloadNotificationsBuilderMap[notificationId] + ?: NotificationCompat.Builder(context, CHANNEL_ID) + downloadNotificationsBuilderMap[notificationId] = notificationBuilder + notificationBuilder + .setGroup("$notificationId") + .setStyle(null) + .setProgress(0, 0, false) + .setContentTitle(null) + .setContentText(null) + .setContentIntent(null) + .setGroupSummary(false) + .setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) + .setOngoing(false) + .setOnlyAlertOnce(true) + .setSmallIcon(android.R.drawable.stat_sys_download_done) + .mActions.clear() + return@getNotificationBuilder notificationBuilder + } + } + + private fun getActionPendingIntent(action: String, downloadId: Int): PendingIntent { + val intent = + Intent(context, DownloadNotificationActionsBroadcastReceiver::class.java).apply { + this.action = action + putExtra(EXTRA_DOWNLOAD_ID, downloadId) + } + return PendingIntent.getBroadcast( + context, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createChannel(channelId: String, context: Context) = + NotificationChannel( + channelId, + context.getString(R.string.app_name), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + setSound(null, null) + enableVibration(false) + } + + private fun getSubtitleText( + context: Context, + downloadNotificationModel: DownloadNotificationModel + ): String { + return when { + downloadNotificationModel.isCompleted -> context.getString(R.string.complete) + downloadNotificationModel.isFailed -> context.getString( + R.string.failed_state, + downloadNotificationModel.error + ) + + downloadNotificationModel.isPaused -> context.getString(R.string.paused_state) + downloadNotificationModel.isQueued -> context.getString(R.string.pending_state) + downloadNotificationModel.etaInMilliSeconds < 0 -> context.getString(R.string.running_state) + else -> Seconds( + downloadNotificationModel.etaInMilliSeconds / 1000L + ).toHumanReadableTime() + } + } + + companion object { + const val CHANNEL_ID = "kiwix_notification_channel_id" + const val ACTION_PAUSE = "action_pause" + const val ACTION_RESUME = "action_resume" + const val ACTION_CANCEL = "action_cancel" + const val EXTRA_DOWNLOAD_ID = "extra_download_id" + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt new file mode 100644 index 0000000000..9f26674529 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt @@ -0,0 +1,44 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.downloader.downloadManager + +data class DownloadNotificationModel( + val downloadId: Int, + val status: Status = Status.NONE, + val progress: Int, + val etaInMilliSeconds: Long, + val title: String, + val description: String?, + val error: String +) { + val isPaused get() = status == Status.PAUSED + val isCompleted get() = status == Status.COMPLETED + val isFailed get() = status == Status.FAILED + val isQueued get() = status == Status.QUEUED + val isDownloading get() = status == Status.DOWNLOADING + val isOnGoingNotification: Boolean + get() { + return when (status) { + Status.QUEUED, + Status.DOWNLOADING -> true + + else -> false + } + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt index 49c2e0ee0c..26fa475a9b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt @@ -43,7 +43,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.BuildConfig import org.kiwix.kiwixmobile.core.CoreApp -import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions @@ -51,6 +50,7 @@ import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToRoomMigrator import org.kiwix.kiwixmobile.core.di.components.CoreActivityComponent import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsBroadcastReceiver import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.extensions.browserIntent import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon @@ -101,6 +101,9 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { @Inject lateinit var downloadManagerBroadcastReceiver: DownloadManagerBroadcastReceiver + @Inject + lateinit var downloadNotificationActionsReceiver: DownloadNotificationActionsBroadcastReceiver + override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.KiwixTheme) super.onCreate(savedInstanceState) @@ -132,6 +135,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { objectBoxToRoomMigrator.migrateObjectBoxDataToRoom() } downloadManagerBroadcastReceiver.let(::registerReceiver) + downloadNotificationActionsReceiver.let(::registerReceiver) } @Suppress("DEPRECATION") @@ -150,6 +154,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { override fun onDestroy() { downloadManagerBroadcastReceiver.let(::unregisterReceiver) + downloadNotificationActionsReceiver.let(::unregisterReceiver) super.onDestroy() } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt index 6eab3cf2e3..6481b63421 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt @@ -54,3 +54,6 @@ const val AUTO_RETRY_MAX_ATTEMPTS = 20 // Default port for http request const val DEFAULT_PORT = 8080 + +const val DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET = 15552000000 * 2 +const val DEFAULT_NOTIFICATION_TIMEOUT_AFTER = 10_000L From d6a8ee9e24f944343bb15a4fc2efeb265e903b27 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 11:13:37 +0530 Subject: [PATCH 09/41] Removed the default notification of download manager --- core/src/main/AndroidManifest.xml | 2 ++ .../downloadManager/DownloadManagerRequester.kt | 2 ++ .../downloadManager/DownloadNotificationManager.kt | 14 +++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index 0a2e1726d9..f1e8811eda 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -21,6 +21,8 @@ android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" /> + diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 474d7dbdf7..d3f9ccdbbc 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.app.DownloadManager import android.app.DownloadManager.Request +import android.app.DownloadManager.Request.VISIBILITY_HIDDEN import android.net.Uri import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -82,4 +83,5 @@ private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: Share ) setTitle(notificationTitle) setAllowedOverMetered(true) + setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index c4606a7e59..db90f20054 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -35,6 +35,7 @@ import org.kiwix.kiwixmobile.core.downloader.model.Seconds import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET +import org.kiwix.kiwixmobile.core.utils.files.Log import javax.inject.Inject class DownloadNotificationManager @Inject constructor( @@ -45,8 +46,11 @@ class DownloadNotificationManager @Inject constructor( private val downloadNotificationsBuilderMap = mutableMapOf() private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationManager.createNotificationChannel(createChannel(CHANNEL_ID, context)) if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) { - notificationManager.createNotificationChannel(createChannel(CHANNEL_ID, context)) + Log.d("NOTIFICATION_MANAGER", "Notification channel created.") + } else { + Log.d("NOTIFICATION_MANAGER", "Notification channel already exists.") } } } @@ -54,6 +58,10 @@ class DownloadNotificationManager @Inject constructor( fun updateNotification( downloadNotificationModel: DownloadNotificationModel ) { + Log.d( + "NOTIFICATION_MANAGER", + "Updating notification for download ID: $downloadNotificationModel" + ) createNotificationChannel() val notificationBuilder = getNotificationBuilder(downloadNotificationModel.downloadId) val smallIcon = if (downloadNotificationModel.progress != 100) { @@ -105,6 +113,10 @@ class DownloadNotificationManager @Inject constructor( else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) } notificationCustomisation(downloadNotificationModel, notificationBuilder, context) + Log.d( + "NOTIFICATION_MANAGER", + "Notification updated and shown for download ID: $downloadNotificationModel" + ) } @SuppressLint("UnspecifiedImmutableFlag") From 30cf3c69ad37973b2e396d65471e7f1274681352 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 12:22:59 +0530 Subject: [PATCH 10/41] Properly showing the notification for different downloads. Corrected the IN_PROGRESS showing in notification when downloading is in_progress state --- .../DownloadNotificationManager.kt | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index db90f20054..37514d8ba8 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -35,7 +35,6 @@ import org.kiwix.kiwixmobile.core.downloader.model.Seconds import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET -import org.kiwix.kiwixmobile.core.utils.files.Log import javax.inject.Inject class DownloadNotificationManager @Inject constructor( @@ -46,11 +45,8 @@ class DownloadNotificationManager @Inject constructor( private val downloadNotificationsBuilderMap = mutableMapOf() private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel(createChannel(CHANNEL_ID, context)) if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) { - Log.d("NOTIFICATION_MANAGER", "Notification channel created.") - } else { - Log.d("NOTIFICATION_MANAGER", "Notification channel already exists.") + notificationManager.createNotificationChannel(createChannel(CHANNEL_ID, context)) } } } @@ -58,10 +54,6 @@ class DownloadNotificationManager @Inject constructor( fun updateNotification( downloadNotificationModel: DownloadNotificationModel ) { - Log.d( - "NOTIFICATION_MANAGER", - "Updating notification for download ID: $downloadNotificationModel" - ) createNotificationChannel() val notificationBuilder = getNotificationBuilder(downloadNotificationModel.downloadId) val smallIcon = if (downloadNotificationModel.progress != 100) { @@ -79,19 +71,19 @@ class DownloadNotificationManager @Inject constructor( if (downloadNotificationModel.isFailed || downloadNotificationModel.isCompleted) { notificationBuilder.setProgress(0, 0, false) } else { - notificationBuilder.setProgress(100, downloadNotificationModel.progress, true) + notificationBuilder.setProgress(100, downloadNotificationModel.progress, false) } when { downloadNotificationModel.isDownloading -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) .addAction( - R.drawable.fetch_notification_cancel, - context.getString(R.string.cancel), - getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) - ).addAction( R.drawable.fetch_notification_pause, context.getString(R.string.tts_pause), getActionPendingIntent(ACTION_PAUSE, downloadNotificationModel.downloadId) + ).addAction( + R.drawable.fetch_notification_cancel, + context.getString(R.string.cancel), + getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) ) downloadNotificationModel.isPaused -> @@ -100,8 +92,7 @@ class DownloadNotificationManager @Inject constructor( R.drawable.fetch_notification_resume, context.getString(R.string.tts_resume), getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) - ) - .addAction( + ).addAction( R.drawable.fetch_notification_cancel, context.getString(R.string.cancel), getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) @@ -113,10 +104,7 @@ class DownloadNotificationManager @Inject constructor( else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) } notificationCustomisation(downloadNotificationModel, notificationBuilder, context) - Log.d( - "NOTIFICATION_MANAGER", - "Notification updated and shown for download ID: $downloadNotificationModel" - ) + notificationManager.notify(downloadNotificationModel.downloadId, notificationBuilder.build()) } @SuppressLint("UnspecifiedImmutableFlag") @@ -203,7 +191,7 @@ class DownloadNotificationManager @Inject constructor( downloadNotificationModel.isPaused -> context.getString(R.string.paused_state) downloadNotificationModel.isQueued -> context.getString(R.string.pending_state) - downloadNotificationModel.etaInMilliSeconds < 0 -> context.getString(R.string.running_state) + downloadNotificationModel.etaInMilliSeconds <= 0 -> context.getString(R.string.running_state) else -> Seconds( downloadNotificationModel.etaInMilliSeconds / 1000L ).toHumanReadableTime() From 6eab724e3d77bb0c87808d8070f149ccd006a47a Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 14:25:36 +0530 Subject: [PATCH 11/41] Calcelling notification when user canceled the downloads --- .../downloadManager/DownloadManagerMonitor.kt | 42 +++--- .../DownloadManagerRequester.kt | 9 +- .../DownloadNotificationManager.kt | 122 ++++++++++-------- .../DownloadNotificationModel.kt | 1 + 4 files changed, 96 insertions(+), 78 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index cc923cf663..74581455f0 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -325,26 +325,34 @@ class DownloadManagerMonitor @Inject constructor( } } downloadRoomDao.update(downloadModel) - downloadNotificationManager.updateNotification( - DownloadNotificationModel( - downloadId = downloadId.toInt(), - status = status, - progress = progress, - etaInMilliSeconds = etaInMilliSeconds, - title = downloadEntity.title, - description = downloadEntity.description, - error = DownloadState.from( - downloadModel.state, - downloadModel.error, - downloadModel.book.url - ).toReadableState(context).toString() - ) - ) + updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) } } } } + private fun updateNotification( + downloadModel: DownloadModel, + title: String, + description: String? + ) { + downloadNotificationManager.updateNotification( + DownloadNotificationModel( + downloadId = downloadModel.downloadId.toInt(), + status = downloadModel.state, + progress = downloadModel.progress, + etaInMilliSeconds = downloadModel.etaInMilliSeconds, + title = title, + description = description, + error = DownloadState.from( + downloadModel.state, + downloadModel.error, + downloadModel.book.url + ).toReadableState(context).toString() + ) + ) + } + fun pauseDownload(downloadId: Long) { synchronized(lock) { updater.onNext { @@ -367,9 +375,7 @@ class DownloadManagerMonitor @Inject constructor( fun cancelDownload(downloadId: Long) { synchronized(lock) { - updater.onNext { - downloadManager.remove(downloadId) - } + handleCancelledDownload(downloadId) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index d3f9ccdbbc..2a46d78f9c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -22,9 +22,6 @@ import android.app.DownloadManager import android.app.DownloadManager.Request import android.app.DownloadManager.Request.VISIBILITY_HIDDEN import android.net.Uri -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest @@ -41,11 +38,7 @@ class DownloadManagerRequester @Inject constructor( downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) override fun cancel(downloadId: Long) { - CoroutineScope(Dispatchers.IO).launch { - downloadManager.remove(downloadId).also { - downloadRoomDao.delete(downloadId) - } - } + downloadManagerMonitor.cancelDownload(downloadId) } override fun retryDownload(downloadId: Long) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 37514d8ba8..85d46e1ab1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -30,7 +30,6 @@ import androidx.core.app.NotificationCompat import org.kiwix.kiwixmobile.core.Intents import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.downloader.fetch.DOWNLOAD_NOTIFICATION_TITLE -import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.Seconds import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER @@ -41,7 +40,6 @@ class DownloadNotificationManager @Inject constructor( private val context: Context, private val notificationManager: NotificationManager ) { - private val downloadNotificationsMap = mutableMapOf() private val downloadNotificationsBuilderMap = mutableMapOf() private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -54,59 +52,72 @@ class DownloadNotificationManager @Inject constructor( fun updateNotification( downloadNotificationModel: DownloadNotificationModel ) { - createNotificationChannel() - val notificationBuilder = getNotificationBuilder(downloadNotificationModel.downloadId) - val smallIcon = if (downloadNotificationModel.progress != 100) { - android.R.drawable.stat_sys_download - } else { - android.R.drawable.stat_sys_download_done - } + synchronized(downloadNotificationsBuilderMap) { + if (shouldUpdateNotification(downloadNotificationModel)) { + createNotificationChannel() + val notificationBuilder = getNotificationBuilder(downloadNotificationModel.downloadId) + val smallIcon = if (downloadNotificationModel.progress != 100) { + android.R.drawable.stat_sys_download + } else { + android.R.drawable.stat_sys_download_done + } - notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSmallIcon(smallIcon) - .setContentTitle(downloadNotificationModel.title) - .setContentText(getSubtitleText(context, downloadNotificationModel)) - .setOngoing(downloadNotificationModel.isOnGoingNotification) - .setGroupSummary(false) - if (downloadNotificationModel.isFailed || downloadNotificationModel.isCompleted) { - notificationBuilder.setProgress(0, 0, false) - } else { - notificationBuilder.setProgress(100, downloadNotificationModel.progress, false) - } - when { - downloadNotificationModel.isDownloading -> - notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) - .addAction( - R.drawable.fetch_notification_pause, - context.getString(R.string.tts_pause), - getActionPendingIntent(ACTION_PAUSE, downloadNotificationModel.downloadId) - ).addAction( - R.drawable.fetch_notification_cancel, - context.getString(R.string.cancel), - getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) - ) - - downloadNotificationModel.isPaused -> - notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) - .addAction( - R.drawable.fetch_notification_resume, - context.getString(R.string.tts_resume), - getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) - ).addAction( - R.drawable.fetch_notification_cancel, - context.getString(R.string.cancel), - getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) - ) - - downloadNotificationModel.isQueued -> - notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) - - else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) + notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setSmallIcon(smallIcon) + .setContentTitle(downloadNotificationModel.title) + .setContentText(getSubtitleText(context, downloadNotificationModel)) + .setOngoing(downloadNotificationModel.isOnGoingNotification) + .setGroupSummary(false) + if (downloadNotificationModel.isFailed || downloadNotificationModel.isCompleted) { + notificationBuilder.setProgress(0, 0, false) + } else { + notificationBuilder.setProgress(100, downloadNotificationModel.progress, false) + } + when { + downloadNotificationModel.isDownloading -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + .addAction( + R.drawable.fetch_notification_pause, + context.getString(R.string.tts_pause), + getActionPendingIntent(ACTION_PAUSE, downloadNotificationModel.downloadId) + ).addAction( + R.drawable.fetch_notification_cancel, + context.getString(R.string.cancel), + getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) + ) + + downloadNotificationModel.isPaused -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + .addAction( + R.drawable.fetch_notification_resume, + context.getString(R.string.tts_resume), + getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) + ).addAction( + R.drawable.fetch_notification_cancel, + context.getString(R.string.cancel), + getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) + ) + + downloadNotificationModel.isQueued -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + + else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) + } + notificationCustomisation(downloadNotificationModel, notificationBuilder, context) + notificationManager.notify( + downloadNotificationModel.downloadId, + notificationBuilder.build() + ) + } else { + // the download is cancelled/paused so remove the notification. + cancelNotification(downloadNotificationModel.downloadId) + } } - notificationCustomisation(downloadNotificationModel, notificationBuilder, context) - notificationManager.notify(downloadNotificationModel.downloadId, notificationBuilder.build()) } + private fun shouldUpdateNotification(downloadNotificationModel: DownloadNotificationModel): Boolean = + !downloadNotificationModel.isCancelled && !downloadNotificationModel.isPaused + @SuppressLint("UnspecifiedImmutableFlag") private fun notificationCustomisation( downloadNotificationModel: DownloadNotificationModel, @@ -132,7 +143,7 @@ class DownloadNotificationManager @Inject constructor( @SuppressLint("RestrictedApi") private fun getNotificationBuilder(notificationId: Int): NotificationCompat.Builder { - synchronized(downloadNotificationsMap) { + synchronized(downloadNotificationsBuilderMap) { val notificationBuilder = downloadNotificationsBuilderMap[notificationId] ?: NotificationCompat.Builder(context, CHANNEL_ID) downloadNotificationsBuilderMap[notificationId] = notificationBuilder @@ -198,6 +209,13 @@ class DownloadNotificationManager @Inject constructor( } } + private fun cancelNotification(notificationId: Int) { + synchronized(downloadNotificationsBuilderMap) { + notificationManager.cancel(notificationId) + downloadNotificationsBuilderMap.remove(notificationId) + } + } + companion object { const val CHANNEL_ID = "kiwix_notification_channel_id" const val ACTION_PAUSE = "action_pause" diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt index 9f26674529..bd5765064a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt @@ -32,6 +32,7 @@ data class DownloadNotificationModel( val isFailed get() = status == Status.FAILED val isQueued get() = status == Status.QUEUED val isDownloading get() = status == Status.DOWNLOADING + val isCancelled get() = status == Status.CANCELLED val isOnGoingNotification: Boolean get() { return when (status) { From 8405c7c6d7f1a215f28a578858eeb0c095e1bbbf Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 15:50:09 +0530 Subject: [PATCH 12/41] Improved the cancelling of downloads --- .../kiwixmobile/core/di/modules/DownloaderModule.kt | 2 -- .../downloadManager/DownloadManagerMonitor.kt | 4 ++++ .../downloadManager/DownloadManagerRequester.kt | 2 -- .../DownloadNotificationActionsBroadcastReceiver.kt | 10 +++++++++- .../downloadManager/DownloadNotificationManager.kt | 8 +++++--- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 5b16df3965..093f190090 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -108,12 +108,10 @@ object DownloaderModule { fun providesDownloadRequester( downloadManager: DownloadManager, sharedPreferenceUtil: SharedPreferenceUtil, - downloadRoomDao: DownloadRoomDao, downloadManagerMonitor: DownloadManagerMonitor ): DownloadRequester = DownloadManagerRequester( downloadManager, sharedPreferenceUtil, - downloadRoomDao, downloadManagerMonitor ) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 74581455f0..bbb2b8fb95 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -326,6 +326,9 @@ class DownloadManagerMonitor @Inject constructor( } downloadRoomDao.update(downloadModel) updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) + } ?: run { + // already downloaded/cancelled so cancel the notification if any running. + downloadNotificationManager.cancelNotification(downloadId.toInt()) } } } @@ -375,6 +378,7 @@ class DownloadManagerMonitor @Inject constructor( fun cancelDownload(downloadId: Long) { synchronized(lock) { + downloadManager.remove(downloadId) handleCancelledDownload(downloadId) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 2a46d78f9c..4d8a46831e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -22,7 +22,6 @@ import android.app.DownloadManager import android.app.DownloadManager.Request import android.app.DownloadManager.Request.VISIBILITY_HIDDEN import android.net.Uri -import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil @@ -31,7 +30,6 @@ import javax.inject.Inject class DownloadManagerRequester @Inject constructor( private val downloadManager: DownloadManager, private val sharedPreferenceUtil: SharedPreferenceUtil, - private val downloadRoomDao: DownloadRoomDao, private val downloadManagerMonitor: DownloadManagerMonitor ) : DownloadRequester { override fun enqueue(downloadRequest: DownloadRequest): Long = diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt index b8af67fe25..b1cfae0df7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt @@ -20,11 +20,13 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.content.Context import android.content.Intent +import android.util.Log import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.EXTRA_DOWNLOAD_ID +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.NOTIFICATION_ACTION import javax.inject.Inject const val DOWNLOAD_NOTIFICATION_ACTION = "download_notification_action" @@ -35,9 +37,15 @@ class DownloadNotificationActionsBroadcastReceiver @Inject constructor( override val action: String = DOWNLOAD_NOTIFICATION_ACTION override fun onIntentWithActionReceived(context: Context, intent: Intent) { + Log.e( + "UPDATED_NOTIFICATION", + "updateNotification: ${intent.getStringExtra(NOTIFICATION_ACTION)}\n" + + "${intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1L)}" + ) val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1L) + val notificationAction = intent.getStringExtra(NOTIFICATION_ACTION) if (downloadId != -1L) { - when (intent.action) { + when (notificationAction) { ACTION_PAUSE -> downloadManagerMonitor.pauseDownload(downloadId) ACTION_RESUME -> downloadManagerMonitor.resumeDownload(downloadId) ACTION_CANCEL -> downloadManagerMonitor.cancelDownload(downloadId) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 85d46e1ab1..fa3e923a6f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -167,12 +167,13 @@ class DownloadNotificationManager @Inject constructor( private fun getActionPendingIntent(action: String, downloadId: Int): PendingIntent { val intent = Intent(context, DownloadNotificationActionsBroadcastReceiver::class.java).apply { - this.action = action + this.action = DOWNLOAD_NOTIFICATION_ACTION + putExtra(NOTIFICATION_ACTION, action) putExtra(EXTRA_DOWNLOAD_ID, downloadId) } return PendingIntent.getBroadcast( context, - 0, + downloadId, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) @@ -209,7 +210,7 @@ class DownloadNotificationManager @Inject constructor( } } - private fun cancelNotification(notificationId: Int) { + fun cancelNotification(notificationId: Int) { synchronized(downloadNotificationsBuilderMap) { notificationManager.cancel(notificationId) downloadNotificationsBuilderMap.remove(notificationId) @@ -218,6 +219,7 @@ class DownloadNotificationManager @Inject constructor( companion object { const val CHANNEL_ID = "kiwix_notification_channel_id" + const val NOTIFICATION_ACTION = "notification_action" const val ACTION_PAUSE = "action_pause" const val ACTION_RESUME = "action_resume" const val ACTION_CANCEL = "action_cancel" From 28472c098833eafb034b3a56f30fe8df5b802ecb Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 16:45:16 +0530 Subject: [PATCH 13/41] Added RECEIVER_NOT_EXPORTED to all broadcasts to make the application compatiable with Android 14. Also, Fixed the clicking is not working in notification --- .../localFileTransfer/WifiDirectManager.kt | 10 ++++++++-- .../core/di/components/CoreComponent.kt | 2 ++ .../core/di/modules/CoreServiceModule.kt | 2 -- .../downloadManager/DownloadManagerMonitor.kt | 3 +++ ...loadNotificationActionsBroadcastReceiver.kt | 18 +++++++----------- .../DownloadNotificationManager.kt | 3 +-- .../core/extensions/ContextExtensions.kt | 15 ++++++++++++++- 7 files changed, 35 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/WifiDirectManager.kt b/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/WifiDirectManager.kt index 7b6872fbea..d8811642be 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/WifiDirectManager.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/localFileTransfer/WifiDirectManager.kt @@ -19,6 +19,7 @@ package org.kiwix.kiwixmobile.localFileTransfer import android.content.BroadcastReceiver import android.content.Context +import android.content.Context.RECEIVER_NOT_EXPORTED import android.content.IntentFilter import android.net.Uri import android.net.wifi.WpsInfo @@ -32,10 +33,10 @@ import android.net.wifi.p2p.WifiP2pManager.Channel import android.net.wifi.p2p.WifiP2pManager.ChannelListener import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener import android.net.wifi.p2p.WifiP2pManager.PeerListListener +import android.os.Build import android.os.Build.VERSION import android.os.Build.VERSION_CODES import android.os.Looper.getMainLooper -import org.kiwix.kiwixmobile.core.utils.files.Log import android.widget.Toast import androidx.lifecycle.LifecycleCoroutineScope import kotlinx.coroutines.launch @@ -44,6 +45,7 @@ import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.FileTransferConfirmation +import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.localFileTransfer.FileItem.FileStatus import org.kiwix.kiwixmobile.localFileTransfer.KiwixWifiP2pBroadcastReceiver.P2pEventListener import java.io.IOException @@ -105,7 +107,11 @@ class WifiDirectManager @Inject constructor( addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) } - context.registerReceiver(receiver, intentFilter) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(receiver, intentFilter, RECEIVER_NOT_EXPORTED) + } else { + context.registerReceiver(receiver, intentFilter) + } } private fun unregisterWifiDirectBroadcastReceiver() = context.unregisterReceiver(receiver) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index da695dadf1..d1617e1eea 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -54,6 +54,7 @@ import org.kiwix.kiwixmobile.core.di.modules.NetworkModule import org.kiwix.kiwixmobile.core.di.modules.SearchModule import org.kiwix.kiwixmobile.core.downloader.Downloader import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsBroadcastReceiver import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.reader.ZimFileReader @@ -120,6 +121,7 @@ interface CoreComponent { fun mutex(): Mutex fun downloadManagerBroadCastReceiver(): DownloadManagerBroadcastReceiver + fun downloadNotificationActionBroadCastReceiver(): DownloadNotificationActionsBroadcastReceiver fun inject(application: CoreApp) fun inject(kiwixWebView: KiwixWebView) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt index aae94c461b..dc2b6b6e4e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/CoreServiceModule.kt @@ -24,8 +24,6 @@ import android.content.Context import dagger.Module import dagger.Provides import org.kiwix.kiwixmobile.core.di.CoreServiceScope -import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerBroadcastReceiver -import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMonitor import org.kiwix.kiwixmobile.core.qr.GenerateQR import org.kiwix.kiwixmobile.core.read_aloud.ReadAloudNotificationManger import org.kiwix.kiwixmobile.core.webserver.KiwixServer diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index bbb2b8fb95..b37272709e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -357,6 +357,7 @@ class DownloadManagerMonitor @Inject constructor( } fun pauseDownload(downloadId: Long) { + Log.e("UPDATE_NOTIFICATION", "pauseDownload: $downloadId") synchronized(lock) { updater.onNext { if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_PAUSED)) { @@ -367,6 +368,7 @@ class DownloadManagerMonitor @Inject constructor( } fun resumeDownload(downloadId: Long) { + Log.e("UPDATE_NOTIFICATION", "resumeDownload: $downloadId") synchronized(lock) { updater.onNext { if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_RUNNING)) { @@ -377,6 +379,7 @@ class DownloadManagerMonitor @Inject constructor( } fun cancelDownload(downloadId: Long) { + Log.e("UPDATE_NOTIFICATION", "cancelDownload: $downloadId") synchronized(lock) { downloadManager.remove(downloadId) handleCancelledDownload(downloadId) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt index b1cfae0df7..c5937b00e7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt @@ -29,7 +29,7 @@ import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificatio import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.NOTIFICATION_ACTION import javax.inject.Inject -const val DOWNLOAD_NOTIFICATION_ACTION = "download_notification_action" +const val DOWNLOAD_NOTIFICATION_ACTION = "org.kiwix.kiwixmobile.download_notification_action" class DownloadNotificationActionsBroadcastReceiver @Inject constructor( private val downloadManagerMonitor: DownloadManagerMonitor @@ -37,18 +37,14 @@ class DownloadNotificationActionsBroadcastReceiver @Inject constructor( override val action: String = DOWNLOAD_NOTIFICATION_ACTION override fun onIntentWithActionReceived(context: Context, intent: Intent) { - Log.e( - "UPDATED_NOTIFICATION", - "updateNotification: ${intent.getStringExtra(NOTIFICATION_ACTION)}\n" + - "${intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1L)}" - ) - val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1L) + val downloadId = intent.getIntExtra(EXTRA_DOWNLOAD_ID, -1) val notificationAction = intent.getStringExtra(NOTIFICATION_ACTION) - if (downloadId != -1L) { + Log.e("UPDATE_NOTIFICATION", "onIntentWithActionReceived: $downloadId , $notificationAction") + if (downloadId != -1) { when (notificationAction) { - ACTION_PAUSE -> downloadManagerMonitor.pauseDownload(downloadId) - ACTION_RESUME -> downloadManagerMonitor.resumeDownload(downloadId) - ACTION_CANCEL -> downloadManagerMonitor.cancelDownload(downloadId) + ACTION_PAUSE -> downloadManagerMonitor.pauseDownload(downloadId.toLong()) + ACTION_RESUME -> downloadManagerMonitor.resumeDownload(downloadId.toLong()) + ACTION_CANCEL -> downloadManagerMonitor.cancelDownload(downloadId.toLong()) } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index fa3e923a6f..eecb87a27c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -166,8 +166,7 @@ class DownloadNotificationManager @Inject constructor( private fun getActionPendingIntent(action: String, downloadId: Int): PendingIntent { val intent = - Intent(context, DownloadNotificationActionsBroadcastReceiver::class.java).apply { - this.action = DOWNLOAD_NOTIFICATION_ACTION + Intent(DOWNLOAD_NOTIFICATION_ACTION).apply { putExtra(NOTIFICATION_ACTION, action) putExtra(EXTRA_DOWNLOAD_ID, downloadId) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ContextExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ContextExtensions.kt index 511bddb53e..c08faa3372 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ContextExtensions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ContextExtensions.kt @@ -19,12 +19,14 @@ package org.kiwix.kiwixmobile.core.extensions import android.content.Context +import android.content.Context.RECEIVER_NOT_EXPORTED import android.content.Intent import android.content.IntentFilter import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.os.Build import android.util.TypedValue import android.widget.Toast import androidx.annotation.AttrRes @@ -53,7 +55,18 @@ fun Context?.toast( } fun Context.registerReceiver(baseBroadcastReceiver: BaseBroadcastReceiver): Intent? = - registerReceiver(baseBroadcastReceiver, IntentFilter(baseBroadcastReceiver.action)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver( + baseBroadcastReceiver, + IntentFilter(baseBroadcastReceiver.action), + RECEIVER_NOT_EXPORTED + ) + } else { + registerReceiver( + baseBroadcastReceiver, + IntentFilter(baseBroadcastReceiver.action) + ) + } val Context.locale: Locale get() = resources.configuration.locales.get(0) From fdeb6bcbe633fa42fd144bf60ed574990928a054 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 17:16:51 +0530 Subject: [PATCH 14/41] Fixed: download is always cancelling when we click on the pause button in notification --- .../downloader/downloadManager/DownloadNotificationManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index eecb87a27c..ad70cfcef1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -170,9 +170,10 @@ class DownloadNotificationManager @Inject constructor( putExtra(NOTIFICATION_ACTION, action) putExtra(EXTRA_DOWNLOAD_ID, downloadId) } + val requestCode = downloadId + action.hashCode() return PendingIntent.getBroadcast( context, - downloadId, + requestCode, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) From 3039232b2c093ee715560c14d13216a0a12721d0 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 18:03:03 +0530 Subject: [PATCH 15/41] Moving the downloads to library fragment after downloading --- .../java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt | 7 ++++++- .../downloader/downloadManager/DownloadManagerMonitor.kt | 3 --- .../DownloadNotificationActionsBroadcastReceiver.kt | 2 -- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt index aee01654cb..5148aab7c9 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt @@ -33,11 +33,16 @@ import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.extensions.deleteFile import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import java.io.File +import javax.inject.Inject @Dao abstract class DownloadRoomDao { + @Inject + lateinit var newBookDao: NewBookDao + @Query("SELECT * FROM DownloadRoomEntity") abstract fun downloadRoomEntity(): Flowable> @@ -57,7 +62,7 @@ abstract class DownloadRoomDao { .takeIf(List::isNotEmpty) ?.let { deleteDownloadsList(it) - // newBookDao.insert(it.map(BooksOnDiskListItem::BookOnDisk)) + newBookDao.insert(it.map(BooksOnDiskListItem::BookOnDisk)) } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index b37272709e..bbb2b8fb95 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -357,7 +357,6 @@ class DownloadManagerMonitor @Inject constructor( } fun pauseDownload(downloadId: Long) { - Log.e("UPDATE_NOTIFICATION", "pauseDownload: $downloadId") synchronized(lock) { updater.onNext { if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_PAUSED)) { @@ -368,7 +367,6 @@ class DownloadManagerMonitor @Inject constructor( } fun resumeDownload(downloadId: Long) { - Log.e("UPDATE_NOTIFICATION", "resumeDownload: $downloadId") synchronized(lock) { updater.onNext { if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_RUNNING)) { @@ -379,7 +377,6 @@ class DownloadManagerMonitor @Inject constructor( } fun cancelDownload(downloadId: Long) { - Log.e("UPDATE_NOTIFICATION", "cancelDownload: $downloadId") synchronized(lock) { downloadManager.remove(downloadId) handleCancelledDownload(downloadId) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt index c5937b00e7..eb0618b07d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationActionsBroadcastReceiver.kt @@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.content.Context import android.content.Intent -import android.util.Log import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE @@ -39,7 +38,6 @@ class DownloadNotificationActionsBroadcastReceiver @Inject constructor( override fun onIntentWithActionReceived(context: Context, intent: Intent) { val downloadId = intent.getIntExtra(EXTRA_DOWNLOAD_ID, -1) val notificationAction = intent.getStringExtra(NOTIFICATION_ACTION) - Log.e("UPDATE_NOTIFICATION", "onIntentWithActionReceived: $downloadId , $notificationAction") if (downloadId != -1) { when (notificationAction) { ACTION_PAUSE -> downloadManagerMonitor.pauseDownload(downloadId.toLong()) From 9b091301bdacc5f062269e94522505d769c88363 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 26 Jul 2024 18:56:48 +0530 Subject: [PATCH 16/41] Fixed some detekt and lint issues --- .../library/OnlineLibraryFragment.kt | 29 -------------- .../libraryView/AvailableSpaceCalculator.kt | 1 - .../zimManager/ZimManageViewModelTest.kt | 1 - .../kiwix/kiwixmobile/core/StorageObserver.kt | 1 - .../core/dao/entities/FetchDownloadEntity.kt | 10 ++--- .../core/data/KiwixRoomDatabase.kt | 1 + .../core/di/modules/ApplicationModule.kt | 5 ++- .../core/di/modules/DownloaderModule.kt | 10 +++-- .../core/downloader/DownloaderImpl.kt | 1 - .../downloadManager/DownloadManagerMonitor.kt | 40 ++++++++++++------- .../DownloadNotificationManager.kt | 16 ++++---- .../core/downloader/downloadManager/Error.kt | 2 + .../core/downloader/downloadManager/Status.kt | 2 + .../downloader/fetch/FetchDownloadMonitor.kt | 2 +- .../core/downloader/model/DownloadRequest.kt | 4 +- .../kiwixmobile/core/StorageObserverTest.kt | 1 - .../download/CustomDownloadViewModel.kt | 3 +- 17 files changed, 59 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index b976d6f05b..d4757b6364 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -60,7 +60,6 @@ import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.downloader.Downloader import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status -import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isManageExternalStoragePermissionGranted import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate @@ -91,7 +90,6 @@ import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.SelectFolder import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.YesNoDialog.WifiOnly import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getPathFromUri import org.kiwix.kiwixmobile.databinding.FragmentDestinationDownloadBinding -import org.kiwix.kiwixmobile.zimManager.Fat32Checker import org.kiwix.kiwixmobile.zimManager.NetworkState import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel import org.kiwix.kiwixmobile.zimManager.libraryView.AvailableSpaceCalculator @@ -213,33 +211,6 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { } } ) - // - val libraryBookEntity = LibraryNetworkEntity.Book().apply { - id = "6a5413cd-0d96-7288-9c1b-5beda035e472" - size = "170628" - url = "https://download.kiwix.org/zim/zimit/100r.co_en_all_2024-06.zim.meta4" - mediaCount = "1433" - articleCount = "320" - favicon = - "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" - title = "100 Rabbits" - description = "Research and test low-tech solutions, and document findings" - language = "eng" - creator = "-" - publisher = "openZIM" - bookName = "100r.co_en_all" - tags = "_ftindex:yes;preppers;_category:other;_pictures:yes;_videos:yes;_details:yes" - date = "2024-06-24" - faviconMimeType = "image/png" - } - onLibraryItemsChange( - arrayListOf( - LibraryListItem.BookItem( - libraryBookEntity, - Fat32Checker.FileSystemState.CanWrite4GbFile - ) - ) - ) } private fun setupMenu() { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt index aac5522778..f48d022f73 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/AvailableSpaceCalculator.kt @@ -24,7 +24,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.core.settings.StorageCalculator diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt index d4212491c8..4e8cec4c44 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt @@ -39,7 +39,6 @@ import org.junit.jupiter.api.extension.ExtendWith import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.data.DataSource diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt index 121e27651b..3a67ac8c3d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/StorageObserver.kt @@ -23,7 +23,6 @@ import io.reactivex.functions.BiFunction import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.runBlocking import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.reader.ZimFileReader diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt index 825882da7e..b0200b2c90 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt @@ -18,8 +18,6 @@ package org.kiwix.kiwixmobile.core.dao.entities import com.tonyodev.fetch2.Download -import com.tonyodev.fetch2.Error -import com.tonyodev.fetch2.Status import io.objectbox.annotation.Entity import io.objectbox.annotation.Id import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book @@ -32,10 +30,10 @@ data class FetchDownloadEntity( val etaInMilliSeconds: Long = -1L, val bytesDownloaded: Long = -1L, val totalSizeOfDownload: Long = -1L, - //@Convert(converter = StatusConverter::class, dbType = Int::class) - //val status: Status = Status.NONE, - //@Convert(converter = ErrorConverter::class, dbType = Int::class) - //val error: Error = Error.NONE, + // @Convert(converter = StatusConverter::class, dbType = Int::class) + // val status: Status = Status.NONE, + // @Convert(converter = ErrorConverter::class, dbType = Int::class) + // val error: Error = Error.NONE, val progress: Int = -1, val bookId: String, val title: String, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt index c144e5d18c..610fb56419 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/KiwixRoomDatabase.kt @@ -112,6 +112,7 @@ abstract class KiwixRoomDatabase : RoomDatabase() { } } + @Suppress("MagicNumber") private val MIGRATION_3_4 = object : Migration(3, 4) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt index c8c6f8da91..42d1f76797 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt @@ -95,8 +95,9 @@ class ApplicationModule { @Provides @Singleton - internal fun provideDownloadMonitor(downloadManagerMonitor: DownloadManagerMonitor) - : DownloadMonitor = downloadManagerMonitor + internal fun provideDownloadMonitor( + downloadManagerMonitor: DownloadManagerMonitor + ): DownloadMonitor = downloadManagerMonitor @Provides @Singleton diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 093f190090..6572ed0d95 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -117,8 +117,9 @@ object DownloaderModule { @Provides @Singleton - fun provideDownloadManagerCallback(downloadManagerMonitor: DownloadManagerMonitor) - : DownloadManagerBroadcastReceiver.Callback = downloadManagerMonitor + fun provideDownloadManagerCallback( + downloadManagerMonitor: DownloadManagerMonitor + ): DownloadManagerBroadcastReceiver.Callback = downloadManagerMonitor @Provides @Singleton @@ -127,8 +128,9 @@ object DownloaderModule { @Provides @Singleton - fun providesDownloadNotificationActionsBroadcastReceiver(downloadManagerMonitor: DownloadManagerMonitor) - : DownloadNotificationActionsBroadcastReceiver = + fun providesDownloadNotificationActionsBroadcastReceiver( + downloadManagerMonitor: DownloadManagerMonitor + ): DownloadNotificationActionsBroadcastReceiver = DownloadNotificationActionsBroadcastReceiver(downloadManagerMonitor) @Provides diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt index 6a6f6d0c1a..18e216eb11 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloaderImpl.kt @@ -21,7 +21,6 @@ package org.kiwix.kiwixmobile.core.downloader import android.annotation.SuppressLint import io.reactivex.Observable import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index bbb2b8fb95..0b68f044e5 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -39,6 +39,11 @@ import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import java.util.concurrent.TimeUnit import javax.inject.Inject +const val ZERO = 0 +const val HUNDERED = 100 +const val THOUSAND = 1000 +const val DEFAULT_INT_VALUE = -1 + class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, private val downloadRoomDao: DownloadRoomDao, @@ -66,6 +71,7 @@ class DownloadManagerMonitor @Inject constructor( } } + @Suppress("MagicNumber") private fun startMonitoringDownloads() { // we have to disable this when no downloads is ongoing // and should re-enable when download started. @@ -183,6 +189,7 @@ class DownloadManagerMonitor @Inject constructor( } } + @Suppress("LongParameterList") private fun handleFailedDownload( downloadId: Long, reason: Int, @@ -261,7 +268,11 @@ class DownloadManagerMonitor @Inject constructor( } private fun calculateProgress(bytesDownloaded: Int, totalBytes: Int): Int = - if (totalBytes > 0) ((bytesDownloaded / totalBytes.toDouble()) * 100).toInt() else 0 + if (totalBytes > ZERO) { + (bytesDownloaded / totalBytes.toDouble()).times(HUNDERED).toInt() + } else { + ZERO + } private fun calculateETA(downloadedFileId: Long, bytesDownloaded: Int, totalBytes: Int): Long { val currentTime = System.currentTimeMillis() @@ -270,16 +281,16 @@ class DownloadManagerMonitor @Inject constructor( } val elapsedTime = currentTime - downloadInfo.startTime - val downloadSpeed = if (elapsedTime > 0) { - (bytesDownloaded - downloadInfo.initialBytesDownloaded) / (elapsedTime / 1000.0) + val downloadSpeed = if (elapsedTime > ZERO) { + (bytesDownloaded - downloadInfo.initialBytesDownloaded) / (elapsedTime / THOUSAND.toFloat()) } else { - 0.0 + ZERO.toFloat() } - return if (downloadSpeed > 0) { - ((totalBytes - bytesDownloaded) / downloadSpeed).toLong() * 1000 + return if (downloadSpeed > ZERO) { + ((totalBytes - bytesDownloaded) / downloadSpeed).toLong() * THOUSAND } else { - 0L + ZERO.toLong() } } @@ -298,14 +309,15 @@ class DownloadManagerMonitor @Inject constructor( } } + @Suppress("LongParameterList") private fun updateDownloadStatus( downloadId: Long, status: Status, error: Error, - progress: Int = -1, - etaInMilliSeconds: Long = -1L, - bytesDownloaded: Int = -1, - totalSizeOfDownload: Int = -1 + progress: Int = DEFAULT_INT_VALUE, + etaInMilliSeconds: Long = DEFAULT_INT_VALUE.toLong(), + bytesDownloaded: Int = DEFAULT_INT_VALUE, + totalSizeOfDownload: Int = DEFAULT_INT_VALUE ) { synchronized(lock) { updater.onNext { @@ -313,14 +325,14 @@ class DownloadManagerMonitor @Inject constructor( val downloadModel = DownloadModel(downloadEntity).apply { state = status this.error = error - if (progress > 0) { + if (progress > ZERO) { this.progress = progress } this.etaInMilliSeconds = etaInMilliSeconds - if (bytesDownloaded != -1) { + if (bytesDownloaded != DEFAULT_INT_VALUE) { this.bytesDownloaded = bytesDownloaded.toLong() } - if (totalSizeOfDownload != -1) { + if (totalSizeOfDownload != DEFAULT_INT_VALUE) { this.totalSizeOfDownload = totalSizeOfDownload.toLong() } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index ad70cfcef1..638b42dc49 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -56,7 +56,7 @@ class DownloadNotificationManager @Inject constructor( if (shouldUpdateNotification(downloadNotificationModel)) { createNotificationChannel() val notificationBuilder = getNotificationBuilder(downloadNotificationModel.downloadId) - val smallIcon = if (downloadNotificationModel.progress != 100) { + val smallIcon = if (downloadNotificationModel.progress != HUNDERED) { android.R.drawable.stat_sys_download } else { android.R.drawable.stat_sys_download_done @@ -69,9 +69,9 @@ class DownloadNotificationManager @Inject constructor( .setOngoing(downloadNotificationModel.isOnGoingNotification) .setGroupSummary(false) if (downloadNotificationModel.isFailed || downloadNotificationModel.isCompleted) { - notificationBuilder.setProgress(0, 0, false) + notificationBuilder.setProgress(ZERO, ZERO, false) } else { - notificationBuilder.setProgress(100, downloadNotificationModel.progress, false) + notificationBuilder.setProgress(HUNDERED, downloadNotificationModel.progress, false) } when { downloadNotificationModel.isDownloading -> @@ -132,7 +132,7 @@ class DownloadNotificationManager @Inject constructor( val pendingIntent = PendingIntent.getActivity( context, - 0, + ZERO, internal, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) @@ -150,7 +150,7 @@ class DownloadNotificationManager @Inject constructor( notificationBuilder .setGroup("$notificationId") .setStyle(null) - .setProgress(0, 0, false) + .setProgress(ZERO, ZERO, false) .setContentTitle(null) .setContentText(null) .setContentIntent(null) @@ -203,9 +203,11 @@ class DownloadNotificationManager @Inject constructor( downloadNotificationModel.isPaused -> context.getString(R.string.paused_state) downloadNotificationModel.isQueued -> context.getString(R.string.pending_state) - downloadNotificationModel.etaInMilliSeconds <= 0 -> context.getString(R.string.running_state) + downloadNotificationModel.etaInMilliSeconds <= ZERO -> + context.getString(R.string.running_state) + else -> Seconds( - downloadNotificationModel.etaInMilliSeconds / 1000L + downloadNotificationModel.etaInMilliSeconds / THOUSAND.toLong() ).toHumanReadableTime() } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt index 596095f245..f165738714 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt @@ -18,6 +18,7 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager +@Suppress("MagicNumber") enum class Error(val value: Int) { UNKNOWN(-1), NONE(0), @@ -35,6 +36,7 @@ enum class Error(val value: Int) { ERROR_UNHANDLED_HTTP_CODE(12); companion object { + @Suppress("ComplexMethod", "MagicNumber") @JvmStatic fun valueOf(value: Int): Error { return when (value) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt index 113ff3263f..da7d922b16 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Status.kt @@ -18,6 +18,7 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager +@Suppress("MagicNumber") enum class Status(val value: Int) { NONE(0), QUEUED(1), @@ -32,6 +33,7 @@ enum class Status(val value: Int) { companion object { + @Suppress("MagicNumber") @JvmStatic fun valueOf(value: Int): Status { return when (value) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt index 5882d6c353..59c7bb9aef 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt @@ -97,7 +97,7 @@ class FetchDownloadMonitor @Inject constructor(fetch: Fetch, fetchDownloadDao: F } private fun update(download: Download) { - //updater.onNext { fetchDownloadDao.update(download) } + // updater.onNext { fetchDownloadDao.update(download) } } private fun delete(download: Download) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt index 9e91097141..7693f30159 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt @@ -29,7 +29,9 @@ data class DownloadRequest(val urlString: String, val notificationTitle: String) fun getDestinationFile(sharedPreferenceUtil: SharedPreferenceUtil): File { val file = - File("${sharedPreferenceUtil.prefStorage}/Kiwix/${StorageUtils.getFileNameFromUrl(urlString)}") + File( + "${sharedPreferenceUtil.prefStorage}/Kiwix/${StorageUtils.getFileNameFromUrl(urlString)}" + ) return file } } diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt index 235b508e9c..beee136801 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/StorageObserverTest.kt @@ -29,7 +29,6 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.reader.ZimFileReader diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt index 760f2a412e..05b990819f 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModel.kt @@ -24,7 +24,6 @@ import io.reactivex.disposables.CompositeDisposable import io.reactivex.processors.PublishProcessor import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Failed import org.kiwix.kiwixmobile.custom.download.Action.ClickedDownload @@ -87,6 +86,7 @@ class CustomDownloadViewModel @Inject constructor( DownloadRequired -> if (action.downloads.isNotEmpty()) DownloadInProgress(action.downloads) else state + is DownloadInProgress -> if (action.downloads.isNotEmpty()) if (action.downloads[0].downloadState is Failed) @@ -95,6 +95,7 @@ class CustomDownloadViewModel @Inject constructor( DownloadInProgress(action.downloads) else DownloadComplete.also { _effects.offer(navigateToCustomReader) } + DownloadComplete -> state } } From fa4f289511d880d31d95f63d04c6eac859990805 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 29 Jul 2024 15:41:23 +0530 Subject: [PATCH 17/41] Fixed remaining detekt and lint issues --- app/detekt_baseline.xml | 2 +- .../kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt | 5 +++-- .../downloader/downloadManager/DownloadManagerMonitor.kt | 5 +++-- .../downloadManager/DownloadNotificationManager.kt | 5 +++-- .../kiwixmobile/core/downloader/model/DownloadRequest.kt | 1 - 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/detekt_baseline.xml b/app/detekt_baseline.xml index db84623e8a..2318ad51db 100644 --- a/app/detekt_baseline.xml +++ b/app/detekt_baseline.xml @@ -5,7 +5,7 @@ EmptyFunctionBlock:None.kt$None${ } EmptyFunctionBlock:SimplePageChangeListener.kt$SimplePageChangeListener${ } LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( booksOnFileSystem: List<BookOnDisk>, activeDownloads: List<DownloadModel>, allLanguages: List<Language>, libraryNetworkEntity: LibraryNetworkEntity, filter: String, fileSystemState: FileSystemState ) - LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: FetchDownloadDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private val kiwixService: KiwixService, private val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil ) + LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private val kiwixService: KiwixService, private val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil ) MagicNumber:LibraryListItem.kt$LibraryListItem.LibraryDownloadItem$1000L MagicNumber:PeerGroupHandshake.kt$PeerGroupHandshake$15000 MagicNumber:ShareFiles.kt$ShareFiles$24 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 6572ed0d95..52c53bff1c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -123,8 +123,9 @@ object DownloaderModule { @Provides @Singleton - fun providesDownloadManagerBroadcastReceiver(callback: DownloadManagerBroadcastReceiver.Callback) - : DownloadManagerBroadcastReceiver = DownloadManagerBroadcastReceiver(callback) + fun providesDownloadManagerBroadcastReceiver( + callback: DownloadManagerBroadcastReceiver.Callback + ): DownloadManagerBroadcastReceiver = DownloadManagerBroadcastReceiver(callback) @Provides @Singleton diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 0b68f044e5..cc83e28915 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -28,7 +28,6 @@ import android.content.Context import android.content.Intent import android.database.Cursor import android.net.Uri -import android.util.Log import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject @@ -36,6 +35,7 @@ import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadState +import org.kiwix.kiwixmobile.core.utils.files.Log import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -71,7 +71,7 @@ class DownloadManagerMonitor @Inject constructor( } } - @Suppress("MagicNumber") + @Suppress("MagicNumber", "CheckResult") private fun startMonitoringDownloads() { // we have to disable this when no downloads is ongoing // and should re-enable when download started. @@ -95,6 +95,7 @@ class DownloadManagerMonitor @Inject constructor( ) } + @Suppress("CheckResult") private fun setupUpdater() { updater.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe( { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 638b42dc49..742f6f8434 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -115,8 +115,9 @@ class DownloadNotificationManager @Inject constructor( } } - private fun shouldUpdateNotification(downloadNotificationModel: DownloadNotificationModel): Boolean = - !downloadNotificationModel.isCancelled && !downloadNotificationModel.isPaused + private fun shouldUpdateNotification( + downloadNotificationModel: DownloadNotificationModel + ): Boolean = !downloadNotificationModel.isCancelled && !downloadNotificationModel.isPaused @SuppressLint("UnspecifiedImmutableFlag") private fun notificationCustomisation( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt index 7693f30159..9b60682f85 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt @@ -18,7 +18,6 @@ package org.kiwix.kiwixmobile.core.downloader.model import android.net.Uri -import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.StorageUtils import java.io.File From a859da3192d37d130d1ed3dd2aa94ba8d965cfc9 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 29 Jul 2024 16:48:18 +0530 Subject: [PATCH 18/41] Improved the moving books functionality after download. * Improved the updating status functionality. * Cancelling the notification if any running and download is completed. * Fixed TestModelFunctions build failure. --- .../core/di/modules/DatabaseModule.kt | 5 ++- .../downloadManager/DownloadManagerMonitor.kt | 42 ++++++++++++------- .../sharedFunctions/TestModelFunctions.kt | 7 ++-- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt index 6afc532871..75c44619f9 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt @@ -101,5 +101,8 @@ open class DatabaseModule { @Singleton @Provides - fun provideDownloadRoomDao(db: KiwixRoomDatabase) = db.downloadRoomDao() + fun provideDownloadRoomDao(db: KiwixRoomDatabase, newBookDao: NewBookDao) = + db.downloadRoomDao().also { + it.newBookDao = newBookDao + } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index cc83e28915..34f232ffb5 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -32,6 +32,7 @@ import io.reactivex.Observable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao +import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadState @@ -323,30 +324,38 @@ class DownloadManagerMonitor @Inject constructor( synchronized(lock) { updater.onNext { downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity -> - val downloadModel = DownloadModel(downloadEntity).apply { - state = status - this.error = error - if (progress > ZERO) { - this.progress = progress - } - this.etaInMilliSeconds = etaInMilliSeconds - if (bytesDownloaded != DEFAULT_INT_VALUE) { - this.bytesDownloaded = bytesDownloaded.toLong() - } - if (totalSizeOfDownload != DEFAULT_INT_VALUE) { - this.totalSizeOfDownload = totalSizeOfDownload.toLong() + if (shouldUpdateStatus(downloadEntity)) { + val downloadModel = DownloadModel(downloadEntity).apply { + state = status + this.error = error + if (progress > ZERO) { + this.progress = progress + } + this.etaInMilliSeconds = etaInMilliSeconds + if (bytesDownloaded != DEFAULT_INT_VALUE) { + this.bytesDownloaded = bytesDownloaded.toLong() + } + if (totalSizeOfDownload != DEFAULT_INT_VALUE) { + this.totalSizeOfDownload = totalSizeOfDownload.toLong() + } } + downloadRoomDao.update(downloadModel) + updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) + return@let } - downloadRoomDao.update(downloadModel) - updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) + cancelNotification(downloadId) } ?: run { // already downloaded/cancelled so cancel the notification if any running. - downloadNotificationManager.cancelNotification(downloadId.toInt()) + cancelNotification(downloadId) } } } } + private fun cancelNotification(downloadId: Long) { + downloadNotificationManager.cancelNotification(downloadId.toInt()) + } + private fun updateNotification( downloadModel: DownloadModel, title: String, @@ -424,6 +433,9 @@ class DownloadManagerMonitor @Inject constructor( } } + private fun shouldUpdateStatus(downloadRoomEntity: DownloadRoomEntity) = + downloadRoomEntity.status != Status.COMPLETED + override fun init() { // empty method to so class does not get reported unused } diff --git a/core/src/sharedTestFunctions/java/org/kiwix/sharedFunctions/TestModelFunctions.kt b/core/src/sharedTestFunctions/java/org/kiwix/sharedFunctions/TestModelFunctions.kt index 7c2e827c58..05ed18abc6 100644 --- a/core/src/sharedTestFunctions/java/org/kiwix/sharedFunctions/TestModelFunctions.kt +++ b/core/src/sharedTestFunctions/java/org/kiwix/sharedFunctions/TestModelFunctions.kt @@ -17,11 +17,10 @@ */ package org.kiwix.sharedFunctions -import com.tonyodev.fetch2.Error -import com.tonyodev.fetch2.Status -import com.tonyodev.fetch2.Status.NONE import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error import org.kiwix.kiwixmobile.core.downloader.model.Base64String import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel @@ -52,7 +51,7 @@ fun downloadModel( etaInMilliSeconds: Long = 0L, bytesDownloaded: Long = 1L, totalSizeOfDownload: Long = 1L, - status: Status = NONE, + status: Status = Status.NONE, error: Error = Error.NONE, progress: Int = 1, book: Book = book() From 6a167459201ae6402e8bd55c6fade30601b18d06 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 29 Jul 2024 18:21:17 +0530 Subject: [PATCH 19/41] Fixed: zim file was not opening when clicking on the notification after the zim file downloaded --- .../main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt | 2 +- .../src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt | 2 +- .../downloader/downloadManager/DownloadNotificationManager.kt | 3 ++- .../core/downloader/fetch/FetchDownloadNotificationManager.kt | 3 +-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt index a39306f6f6..ed2432bd1a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt @@ -38,7 +38,7 @@ import org.kiwix.kiwixmobile.BuildConfig import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.dao.NewBookDao -import org.kiwix.kiwixmobile.core.downloader.fetch.DOWNLOAD_NOTIFICATION_TITLE +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION import org.kiwix.kiwixmobile.core.utils.INTERNAL_SELECT_POSITION diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt index b01e22ee30..1a7ee19689 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt @@ -90,7 +90,7 @@ class NewBookDao @Inject constructor(private val box: Box) { fun bookMatching(downloadTitle: String) = box.query { endsWith( - BookOnDiskEntity_.file, downloadTitle, + BookOnDiskEntity_.title, downloadTitle, QueryBuilder.StringOrder.CASE_INSENSITIVE ) }.findFirst() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 742f6f8434..56ec5bb932 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -29,13 +29,14 @@ import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import org.kiwix.kiwixmobile.core.Intents import org.kiwix.kiwixmobile.core.R -import org.kiwix.kiwixmobile.core.downloader.fetch.DOWNLOAD_NOTIFICATION_TITLE import org.kiwix.kiwixmobile.core.downloader.model.Seconds import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET import javax.inject.Inject +const val DOWNLOAD_NOTIFICATION_TITLE = "OPEN_ZIM_FILE" + class DownloadNotificationManager @Inject constructor( private val context: Context, private val notificationManager: NotificationManager diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt index cabf8c54b2..9cdfc3254a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt @@ -50,10 +50,9 @@ import org.kiwix.kiwixmobile.core.Intents import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao +import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE import org.kiwix.kiwixmobile.core.main.CoreMainActivity -const val DOWNLOAD_NOTIFICATION_TITLE = "OPEN_ZIM_FILE" - class FetchDownloadNotificationManager( private val context: Context, private val fetchDownloadDao: FetchDownloadDao From f640dd1c19f519c443e1efe8e18092b497036379 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 29 Jul 2024 18:39:36 +0530 Subject: [PATCH 20/41] Improved the file opening while clicking the download notification, because two zim files can have the same title --- core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt | 2 +- .../core/downloader/downloadManager/DownloadManagerMonitor.kt | 1 + .../downloader/downloadManager/DownloadNotificationManager.kt | 2 +- .../downloader/downloadManager/DownloadNotificationModel.kt | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt index 1a7ee19689..b01e22ee30 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt @@ -90,7 +90,7 @@ class NewBookDao @Inject constructor(private val box: Box) { fun bookMatching(downloadTitle: String) = box.query { endsWith( - BookOnDiskEntity_.title, downloadTitle, + BookOnDiskEntity_.file, downloadTitle, QueryBuilder.StringOrder.CASE_INSENSITIVE ) }.findFirst() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 34f232ffb5..b68f07e8be 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -369,6 +369,7 @@ class DownloadManagerMonitor @Inject constructor( etaInMilliSeconds = downloadModel.etaInMilliSeconds, title = title, description = description, + filePath = downloadModel.file, error = DownloadState.from( downloadModel.state, downloadModel.error, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 56ec5bb932..b5a7e66318 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -129,7 +129,7 @@ class DownloadNotificationManager @Inject constructor( if (downloadNotificationModel.isCompleted) { val internal = Intents.internal(CoreMainActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - putExtra(DOWNLOAD_NOTIFICATION_TITLE, downloadNotificationModel.title) + putExtra(DOWNLOAD_NOTIFICATION_TITLE, downloadNotificationModel.filePath) } val pendingIntent = PendingIntent.getActivity( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt index bd5765064a..17f2de2c9e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationModel.kt @@ -25,6 +25,7 @@ data class DownloadNotificationModel( val etaInMilliSeconds: Long, val title: String, val description: String?, + val filePath: String?, val error: String ) { val isPaused get() = status == Status.PAUSED From 9a2acea046298ccb50d9720455c0b17c472501a7 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 29 Jul 2024 19:15:11 +0530 Subject: [PATCH 21/41] Implemented the retry download functionality. * Removed unnecessary code. --- .../kiwixmobile/core/dao/DownloadRoomDao.kt | 2 +- .../downloadManager/DownloadManagerMonitor.kt | 2 +- .../DownloadManagerRequester.kt | 38 +++++++++++++------ .../core/downloader/model/DownloadRequest.kt | 2 +- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt index 5148aab7c9..a3d95885ca 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt @@ -107,7 +107,7 @@ abstract class DownloadRoomDao { sharedPreferenceUtil: SharedPreferenceUtil ) { if (doesNotAlreadyExist(book)) { - val downloadRequest = DownloadRequest(url, book.title) + val downloadRequest = DownloadRequest(url) saveDownload( DownloadRoomEntity( url, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index b68f07e8be..0df77ff145 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -47,7 +47,7 @@ const val DEFAULT_INT_VALUE = -1 class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, - private val downloadRoomDao: DownloadRoomDao, + val downloadRoomDao: DownloadRoomDao, private val context: Context, private val downloadNotificationManager: DownloadNotificationManager ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 4d8a46831e..385464d35e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -22,9 +22,13 @@ import android.app.DownloadManager import android.app.DownloadManager.Request import android.app.DownloadManager.Request.VISIBILITY_HIDDEN import android.net.Uri +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.core.utils.files.Log import javax.inject.Inject class DownloadManagerRequester @Inject constructor( @@ -41,17 +45,28 @@ class DownloadManagerRequester @Inject constructor( override fun retryDownload(downloadId: Long) { // Retry the download by enqueuing it again with the same request - // CoroutineScope(Dispatchers.IO).launch { - // val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) - // downloadEntity?.let { - // val downloadRequest = DownloadRequest( - // uri = Uri.parse(it.uri), - // notificationTitle = it.notificationTitle, - // destinationFile = it.destinationFile - // ) - // enqueue(downloadRequest) - // } - // } + CoroutineScope(Dispatchers.IO).launch { + try { + downloadManagerMonitor + .downloadRoomDao + .getEntityForDownloadId(downloadId)?.let { downloadRoomEntity -> + downloadRoomEntity.file?.let { + val downloadRequest = DownloadRequest(urlString = it) + val newDownloadEntity = downloadRoomEntity.copy(downloadId = enqueue(downloadRequest)) + // cancel the previous download and its data from database and fileSystem. + cancel(downloadId) + // save the new downloads into the database so that it will show + // this new downloads on the download screen. + downloadManagerMonitor.downloadRoomDao.saveDownload(newDownloadEntity) + } + } + } catch (ignore: Exception) { + Log.e( + "DOWNLOAD_MANAGER", + "Could not retry the download. Original exception = $ignore" + ) + } + } } override fun pauseResumeDownload(downloadId: Long, isPause: Boolean) { @@ -72,7 +87,6 @@ private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: Share else Request.NETWORK_MOBILE ) - setTitle(notificationTitle) setAllowedOverMetered(true) setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt index 9b60682f85..24ec973856 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadRequest.kt @@ -22,7 +22,7 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.StorageUtils import java.io.File -data class DownloadRequest(val urlString: String, val notificationTitle: String) { +data class DownloadRequest(val urlString: String) { val uri: Uri get() = Uri.parse(urlString) From d398c47c3fb993b3eab0efbae661b234ed84a430 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 30 Jul 2024 12:31:16 +0530 Subject: [PATCH 22/41] Fixed: Notification "pause" text and notification icons. * Improved variable naming and placed them in appropriate locations. --- .../DownloadNotificationManager.kt | 50 ++++++++++++------- .../kiwix/kiwixmobile/core/utils/Constants.kt | 1 + core/src/main/res/values/strings.xml | 1 + 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index b5a7e66318..8b08baba88 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -33,6 +33,8 @@ import org.kiwix.kiwixmobile.core.downloader.model.Seconds import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER import org.kiwix.kiwixmobile.core.utils.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET +import org.kiwix.kiwixmobile.core.utils.DOWNLOAD_NOTIFICATION_CHANNEL_ID +import java.util.Locale import javax.inject.Inject const val DOWNLOAD_NOTIFICATION_TITLE = "OPEN_ZIM_FILE" @@ -44,8 +46,8 @@ class DownloadNotificationManager @Inject constructor( private val downloadNotificationsBuilderMap = mutableMapOf() private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) { - notificationManager.createNotificationChannel(createChannel(CHANNEL_ID, context)) + if (notificationManager.getNotificationChannel(DOWNLOAD_NOTIFICATION_CHANNEL_ID) == null) { + notificationManager.createNotificationChannel(createChannel(context)) } } } @@ -78,25 +80,25 @@ class DownloadNotificationManager @Inject constructor( downloadNotificationModel.isDownloading -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) .addAction( - R.drawable.fetch_notification_pause, - context.getString(R.string.tts_pause), - getActionPendingIntent(ACTION_PAUSE, downloadNotificationModel.downloadId) - ).addAction( - R.drawable.fetch_notification_cancel, + R.drawable.ic_baseline_stop, context.getString(R.string.cancel), getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) + ).addAction( + R.drawable.ic_baseline_pause, + getPauseOrResumeTitle(true), + getActionPendingIntent(ACTION_PAUSE, downloadNotificationModel.downloadId) ) downloadNotificationModel.isPaused -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) .addAction( - R.drawable.fetch_notification_resume, - context.getString(R.string.tts_resume), - getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) - ).addAction( - R.drawable.fetch_notification_cancel, + R.drawable.ic_baseline_stop, context.getString(R.string.cancel), getActionPendingIntent(ACTION_CANCEL, downloadNotificationModel.downloadId) + ).addAction( + R.drawable.ic_baseline_play, + getPauseOrResumeTitle(false), + getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) ) downloadNotificationModel.isQueued -> @@ -116,6 +118,21 @@ class DownloadNotificationManager @Inject constructor( } } + private fun getPauseOrResumeTitle(isPause: Boolean): String { + val pauseOrResumeTitle = if (isPause) { + context.getString(R.string.tts_pause) + } else { + context.getString(R.string.tts_resume) + } + return pauseOrResumeTitle.replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase(Locale.ROOT) + } else { + "$it" + } + } + } + private fun shouldUpdateNotification( downloadNotificationModel: DownloadNotificationModel ): Boolean = !downloadNotificationModel.isCancelled && !downloadNotificationModel.isPaused @@ -147,7 +164,7 @@ class DownloadNotificationManager @Inject constructor( private fun getNotificationBuilder(notificationId: Int): NotificationCompat.Builder { synchronized(downloadNotificationsBuilderMap) { val notificationBuilder = downloadNotificationsBuilderMap[notificationId] - ?: NotificationCompat.Builder(context, CHANNEL_ID) + ?: NotificationCompat.Builder(context, DOWNLOAD_NOTIFICATION_CHANNEL_ID) downloadNotificationsBuilderMap[notificationId] = notificationBuilder notificationBuilder .setGroup("$notificationId") @@ -182,10 +199,10 @@ class DownloadNotificationManager @Inject constructor( } @RequiresApi(Build.VERSION_CODES.O) - private fun createChannel(channelId: String, context: Context) = + private fun createChannel(context: Context) = NotificationChannel( - channelId, - context.getString(R.string.app_name), + DOWNLOAD_NOTIFICATION_CHANNEL_ID, + context.getString(R.string.download_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT ).apply { setSound(null, null) @@ -222,7 +239,6 @@ class DownloadNotificationManager @Inject constructor( } companion object { - const val CHANNEL_ID = "kiwix_notification_channel_id" const val NOTIFICATION_ACTION = "notification_action" const val ACTION_PAUSE = "action_pause" const val ACTION_RESUME = "action_resume" diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt index 6481b63421..024c65bc8a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt @@ -40,6 +40,7 @@ const val EXTRA_IS_WIDGET_VOICE = "isWidgetVoice" const val HOTSPOT_SERVICE_CHANNEL_ID = "hotspotService" const val OLD_PROVIDER_DOMAIN = "org.kiwix.zim.base" const val READ_ALOUD_SERVICE_CHANNEL_ID = "readAloudService" +const val DOWNLOAD_NOTIFICATION_CHANNEL_ID = "kiwixDownloadNotificationChannel" // For Storage select dialog const val INTERNAL_SELECT_POSITION = 0 diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index f9abd9b150..b6a3d68370 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -121,6 +121,7 @@ No files here Insufficient space to download. Download + Download Notification Channel Name Space Available: Simple No Pictures From 71db5fc7d100600bd6fa5ee9530f6ae7ac07f8c1 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 30 Jul 2024 13:00:33 +0530 Subject: [PATCH 23/41] Improved download monitoring efficiency by disposing observable when no downloads are ongoing. * This change ensures that the observable is only active when necessary, reducing unnecessary resource usage and avoiding redundant requests to the DownloadManager. --- .../downloadManager/DownloadManagerMonitor.kt | 28 ++++++++++++++----- .../DownloadManagerRequester.kt | 7 +++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 0df77ff145..1754b25fc2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -29,6 +29,7 @@ import android.content.Intent import android.database.Cursor import android.net.Uri import io.reactivex.Observable +import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao @@ -55,6 +56,7 @@ class DownloadManagerMonitor @Inject constructor( private val updater = PublishSubject.create<() -> Unit>() private val lock = Any() private val downloadInfoMap = mutableMapOf() + private var monitoringDisposable: Disposable? = null init { startMonitoringDownloads() @@ -72,18 +74,30 @@ class DownloadManagerMonitor @Inject constructor( } } - @Suppress("MagicNumber", "CheckResult") - private fun startMonitoringDownloads() { - // we have to disable this when no downloads is ongoing - // and should re-enable when download started. - Observable.interval(0, 5, TimeUnit.SECONDS) + /** + * Starts monitoring ongoing downloads using a periodic observable. + * This method sets up an observable that runs every 5 seconds to check the status of downloads. + * It only starts the monitoring process if it's not already running and disposes of the observable + * when there are no ongoing downloads to avoid unnecessary resource usage. + */ + @Suppress("MagicNumber") + fun startMonitoringDownloads() { + // Check if monitoring is already active. If it is, do nothing. + if (monitoringDisposable?.isDisposed == false) return + monitoringDisposable = Observable.interval(0, 5, TimeUnit.SECONDS) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .subscribe( { try { - if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { - checkDownloads() + synchronized(lock) { + if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { + checkDownloads() + } else { + // dispose to avoid unnecessary request to downloadManager + // when there is no download ongoing. + monitoringDisposable?.dispose() + } } } catch (ignore: Exception) { Log.i( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 385464d35e..f5832aacee 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -38,6 +38,10 @@ class DownloadManagerRequester @Inject constructor( ) : DownloadRequester { override fun enqueue(downloadRequest: DownloadRequest): Long = downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) + .also { + // Start monitoring downloads after enqueuing a new download request. + downloadManagerMonitor.startMonitoringDownloads() + } override fun cancel(downloadId: Long) { downloadManagerMonitor.cancelDownload(downloadId) @@ -59,6 +63,9 @@ class DownloadManagerRequester @Inject constructor( // this new downloads on the download screen. downloadManagerMonitor.downloadRoomDao.saveDownload(newDownloadEntity) } + }.also { + // Start monitoring downloads after retrying. + downloadManagerMonitor.startMonitoringDownloads() } } catch (ignore: Exception) { Log.e( From 4fb53ed5df606e9bc72bfc28a7aa7dfa8301bac4 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 30 Jul 2024 15:16:10 +0530 Subject: [PATCH 24/41] Fixed downloading progress is not showing after starting the download. --- .../org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt | 4 +++- .../kiwixmobile/core/downloader/DownloadRequester.kt | 1 + .../downloadManager/DownloadManagerMonitor.kt | 8 ++++++-- .../downloadManager/DownloadManagerRequester.kt | 11 ++++++----- .../core/downloader/fetch/FetchDownloadRequester.kt | 4 ++++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt index a3d95885ca..f90d8ea868 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/DownloadRoomDao.kt @@ -115,7 +115,9 @@ abstract class DownloadRoomDao { book = book, file = downloadRequest.getDestinationFile(sharedPreferenceUtil).path ) - ) + ).also { + downloadRequester.onDownloadAdded() + } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloadRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloadRequester.kt index 063bbf7138..d26ad6fd6b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloadRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/DownloadRequester.kt @@ -24,4 +24,5 @@ interface DownloadRequester { fun cancel(downloadId: Long) fun retryDownload(downloadId: Long) fun pauseResumeDownload(downloadId: Long, isPause: Boolean) + fun onDownloadAdded() } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 1754b25fc2..40112634ec 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -84,13 +84,17 @@ class DownloadManagerMonitor @Inject constructor( fun startMonitoringDownloads() { // Check if monitoring is already active. If it is, do nothing. if (monitoringDisposable?.isDisposed == false) return - monitoringDisposable = Observable.interval(0, 5, TimeUnit.SECONDS) + monitoringDisposable = Observable.interval(ZERO.toLong(), 5, TimeUnit.SECONDS) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .subscribe( { try { synchronized(lock) { + Log.e( + "DOWNLOAD_MONITOR", + "startMonitoringDownloads: ${downloadRoomDao.downloads().blockingFirst()}" + ) if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { checkDownloads() } else { @@ -425,7 +429,7 @@ class DownloadManagerMonitor @Inject constructor( downloadId: Long, control: Int ): Boolean { - Log.e("PAUSED", "pauseResumeDownloadInDownloadManagerContentResolver: $control") + Log.e("DOWNLOAD_MONITOR", "pauseResumeDownloadInDownloadManagerContentResolver: $control") return try { // Update the status to paused/resumed in the database val contentValues = ContentValues().apply { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index f5832aacee..eba7549f33 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -38,10 +38,11 @@ class DownloadManagerRequester @Inject constructor( ) : DownloadRequester { override fun enqueue(downloadRequest: DownloadRequest): Long = downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) - .also { - // Start monitoring downloads after enqueuing a new download request. - downloadManagerMonitor.startMonitoringDownloads() - } + + override fun onDownloadAdded() { + // Start monitoring downloads after enqueuing a new download request. + downloadManagerMonitor.startMonitoringDownloads() + } override fun cancel(downloadId: Long) { downloadManagerMonitor.cancelDownload(downloadId) @@ -65,7 +66,7 @@ class DownloadManagerRequester @Inject constructor( } }.also { // Start monitoring downloads after retrying. - downloadManagerMonitor.startMonitoringDownloads() + onDownloadAdded() } } catch (ignore: Exception) { Log.e( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt index 3da6946d07..6497a12105 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt @@ -52,6 +52,10 @@ class FetchDownloadRequester @Inject constructor( else fetch.pause(downloadId.toInt()) } + + override fun onDownloadAdded() { + // empty function + } } private fun DownloadRequest.toFetchRequest(sharedPreferenceUtil: SharedPreferenceUtil) = From b2d2c5c2425a3198e3bbdb37e2a02e441fbe7130 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 30 Jul 2024 17:19:27 +0530 Subject: [PATCH 25/41] Fixed retry download is not working in some conditions --- .../downloader/downloadManager/DownloadManagerRequester.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index eba7549f33..5f14b86b73 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -55,9 +55,10 @@ class DownloadManagerRequester @Inject constructor( downloadManagerMonitor .downloadRoomDao .getEntityForDownloadId(downloadId)?.let { downloadRoomEntity -> - downloadRoomEntity.file?.let { + downloadRoomEntity.url?.let { val downloadRequest = DownloadRequest(urlString = it) - val newDownloadEntity = downloadRoomEntity.copy(downloadId = enqueue(downloadRequest)) + val newDownloadEntity = + downloadRoomEntity.copy(downloadId = enqueue(downloadRequest), id = 0) // cancel the previous download and its data from database and fileSystem. cancel(downloadId) // save the new downloads into the database so that it will show From 583038154bfcf0221d790dd37b05e7c1f856d2da Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 30 Jul 2024 19:10:16 +0530 Subject: [PATCH 26/41] Fixing pause/resume functionlaity is not working on android 10 and above --- .../core/dao/entities/DownloadRoomEntity.kt | 4 +- .../downloadManager/DownloadManagerMonitor.kt | 66 ++++++++++++++++--- .../DownloadManagerRequester.kt | 8 ++- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt index 1d1256eb07..1a049bae46 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/DownloadRoomEntity.kt @@ -34,12 +34,12 @@ data class DownloadRoomEntity( var downloadId: Long, val file: String? = null, val etaInMilliSeconds: Long = -1L, - val bytesDownloaded: Long = -1L, + var bytesDownloaded: Long = -1L, val totalSizeOfDownload: Long = -1L, @Convert(converter = StatusConverter::class, dbType = Int::class) var status: Status = Status.NONE, @Convert(converter = ErrorConverter::class, dbType = Int::class) - val error: Error = Error.NONE, + var error: Error = Error.NONE, val progress: Int = -1, val bookId: String, val title: String, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 40112634ec..25dd121902 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -20,8 +20,6 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.annotation.SuppressLint import android.app.DownloadManager -import android.app.DownloadManager.STATUS_PAUSED -import android.app.DownloadManager.STATUS_RUNNING import android.content.ContentUris import android.content.ContentValues import android.content.Context @@ -36,8 +34,11 @@ import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.downloader.model.DownloadState +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.files.Log +import java.io.File import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -50,7 +51,8 @@ class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, val downloadRoomDao: DownloadRoomDao, private val context: Context, - private val downloadNotificationManager: DownloadNotificationManager + private val downloadNotificationManager: DownloadNotificationManager, + private val sharedPreferenceUtil: SharedPreferenceUtil ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val updater = PublishSubject.create<() -> Unit>() @@ -91,9 +93,9 @@ class DownloadManagerMonitor @Inject constructor( { try { synchronized(lock) { - Log.e( + Log.i( "DOWNLOAD_MONITOR", - "startMonitoringDownloads: ${downloadRoomDao.downloads().blockingFirst()}" + "Couldn't ${downloadRoomDao.downloads().blockingFirst()}" ) if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { checkDownloads() @@ -341,6 +343,7 @@ class DownloadManagerMonitor @Inject constructor( ) { synchronized(lock) { updater.onNext { + Log.e("DOWNLOAD_MONITOR", "update status: $status") downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity -> if (shouldUpdateStatus(downloadEntity)) { val downloadModel = DownloadModel(downloadEntity).apply { @@ -400,9 +403,20 @@ class DownloadManagerMonitor @Inject constructor( fun pauseDownload(downloadId: Long) { synchronized(lock) { updater.onNext { - if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_PAUSED)) { + val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) + downloadEntity?.let { + // Save the file path and the downloaded bytes + val downloadedBytes = getBytesDownloaded(downloadId) + it.bytesDownloaded = downloadedBytes.toLong() + downloadRoomDao.update(DownloadModel(it)) + + // Cancel the current download + downloadManager.remove(downloadId) updateDownloadStatus(downloadId, Status.PAUSED, Error.NONE) } + // if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, 1)) { + // updateDownloadStatus(downloadId, Status.PAUSED, Error.NONE) + // } } } } @@ -410,9 +424,33 @@ class DownloadManagerMonitor @Inject constructor( fun resumeDownload(downloadId: Long) { synchronized(lock) { updater.onNext { - if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, STATUS_RUNNING)) { - updateDownloadStatus(downloadId, Status.QUEUED, Error.NONE) + try { + val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) + downloadEntity?.let { + val file = File(it.file) + val downloadedBytes = it.bytesDownloaded + val downloadRequest = DownloadRequest(it.url ?: "") + + val newDownloadId = + downloadManager.enqueue( + downloadRequest.toDownloadManagerRequest( + sharedPreferenceUtil, + downloadedBytes + ) + ) + it.downloadId = newDownloadId + it.status = Status.QUEUED + it.error = Error.NONE + val downloadModel = DownloadModel(it) + downloadRoomDao.update(downloadModel) + updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) + } + } catch (ignore: Exception) { + ignore.printStackTrace() } + // if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, 0)) { + // updateDownloadStatus(downloadId, Status.QUEUED, Error.NONE) + // } } } } @@ -424,6 +462,18 @@ class DownloadManagerMonitor @Inject constructor( } } + @SuppressLint("Range") + private fun getBytesDownloaded(downloadId: Long): Int { + downloadManager.query(DownloadManager.Query().setFilterById(downloadId)).use { cursor -> + if (cursor.moveToFirst()) { + return@getBytesDownloaded cursor.getInt( + cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) + ) + } + } + return ZERO + } + @SuppressLint("Range") private fun pauseResumeDownloadInDownloadManagerContentResolver( downloadId: Long, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 5f14b86b73..e0a9e91082 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -87,7 +87,10 @@ class DownloadManagerRequester @Inject constructor( } } -private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: SharedPreferenceUtil) = +fun DownloadRequest.toDownloadManagerRequest( + sharedPreferenceUtil: SharedPreferenceUtil, + bytesDownloaded: Long = ZERO.toLong() +) = DownloadManager.Request(uri).apply { setDestinationUri(Uri.fromFile(getDestinationFile(sharedPreferenceUtil))) setAllowedNetworkTypes( @@ -98,4 +101,7 @@ private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: Share ) setAllowedOverMetered(true) setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. + if (bytesDownloaded > 0) { + addRequestHeader("Range", "bytes=$bytesDownloaded-") + } } From cb8829ea5f438ac178f9fba0c4b0b3ac8359e0c3 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 1 Aug 2024 18:13:52 +0530 Subject: [PATCH 27/41] Fixed the resume is not working properly. --- .../downloadManager/DownloadManagerMonitor.kt | 109 +++++++----------- .../DownloadManagerRequester.kt | 6 +- 2 files changed, 44 insertions(+), 71 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 25dd121902..ee608892ff 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager import android.annotation.SuppressLint import android.app.DownloadManager +import android.app.DownloadManager.COLUMN_STATUS import android.content.ContentUris import android.content.ContentValues import android.content.Context @@ -34,11 +35,9 @@ import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel -import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.files.Log -import java.io.File import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -47,6 +46,18 @@ const val HUNDERED = 100 const val THOUSAND = 1000 const val DEFAULT_INT_VALUE = -1 +/* + These below values of android.provider.Downloads.Impl class, + there is no direct way to access them so we defining the values + from https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/provider/Downloads.java + */ +const val CONTROL_PAUSE = 1 +const val CONTROL_RUN = 0 +const val STATUS_RUNNING = 192 +const val STATUS_PAUSED_BY_APP = 193 +const val COLUMN_CONTROL = "control" +val downloadBaseUri: Uri = Uri.parse("content://downloads/my_downloads") + class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, val downloadRoomDao: DownloadRoomDao, @@ -343,11 +354,20 @@ class DownloadManagerMonitor @Inject constructor( ) { synchronized(lock) { updater.onNext { - Log.e("DOWNLOAD_MONITOR", "update status: $status") downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity -> if (shouldUpdateStatus(downloadEntity)) { val downloadModel = DownloadModel(downloadEntity).apply { - state = status + if (status == Status.PAUSED && downloadEntity.status == Status.QUEUED) { + // Check if the user has resumed the download. + // Do not update the download status immediately since the download manager + // takes some time to actually resume the download. During this time, + // it will still return the paused state. + // By not updating the status right away, we ensure that the user + // sees the "Pending" state, indicating that the download is in the process + // of resuming. + } else { + state = status + } this.error = error if (progress > ZERO) { this.progress = progress @@ -403,20 +423,14 @@ class DownloadManagerMonitor @Inject constructor( fun pauseDownload(downloadId: Long) { synchronized(lock) { updater.onNext { - val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) - downloadEntity?.let { - // Save the file path and the downloaded bytes - val downloadedBytes = getBytesDownloaded(downloadId) - it.bytesDownloaded = downloadedBytes.toLong() - downloadRoomDao.update(DownloadModel(it)) - - // Cancel the current download - downloadManager.remove(downloadId) + if (pauseResumeDownloadInDownloadManagerContentResolver( + downloadId, + CONTROL_PAUSE, + STATUS_PAUSED_BY_APP + ) + ) { updateDownloadStatus(downloadId, Status.PAUSED, Error.NONE) } - // if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, 1)) { - // updateDownloadStatus(downloadId, Status.PAUSED, Error.NONE) - // } } } } @@ -424,33 +438,14 @@ class DownloadManagerMonitor @Inject constructor( fun resumeDownload(downloadId: Long) { synchronized(lock) { updater.onNext { - try { - val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) - downloadEntity?.let { - val file = File(it.file) - val downloadedBytes = it.bytesDownloaded - val downloadRequest = DownloadRequest(it.url ?: "") - - val newDownloadId = - downloadManager.enqueue( - downloadRequest.toDownloadManagerRequest( - sharedPreferenceUtil, - downloadedBytes - ) - ) - it.downloadId = newDownloadId - it.status = Status.QUEUED - it.error = Error.NONE - val downloadModel = DownloadModel(it) - downloadRoomDao.update(downloadModel) - updateNotification(downloadModel, downloadEntity.title, downloadEntity.description) - } - } catch (ignore: Exception) { - ignore.printStackTrace() + if (pauseResumeDownloadInDownloadManagerContentResolver( + downloadId, + CONTROL_RUN, + STATUS_RUNNING + ) + ) { + updateDownloadStatus(downloadId, Status.QUEUED, Error.NONE) } - // if (pauseResumeDownloadInDownloadManagerContentResolver(downloadId, 0)) { - // updateDownloadStatus(downloadId, Status.QUEUED, Error.NONE) - // } } } } @@ -462,39 +457,21 @@ class DownloadManagerMonitor @Inject constructor( } } - @SuppressLint("Range") - private fun getBytesDownloaded(downloadId: Long): Int { - downloadManager.query(DownloadManager.Query().setFilterById(downloadId)).use { cursor -> - if (cursor.moveToFirst()) { - return@getBytesDownloaded cursor.getInt( - cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) - ) - } - } - return ZERO - } - @SuppressLint("Range") private fun pauseResumeDownloadInDownloadManagerContentResolver( downloadId: Long, - control: Int + control: Int, + status: Int ): Boolean { - Log.e("DOWNLOAD_MONITOR", "pauseResumeDownloadInDownloadManagerContentResolver: $control") return try { // Update the status to paused/resumed in the database val contentValues = ContentValues().apply { - put("control", control) + put(COLUMN_CONTROL, control) + put(COLUMN_STATUS, status) } - val uri = - ContentUris.withAppendedId(Uri.parse("content://downloads/my_downloads"), downloadId) - val downloadEntity = downloadRoomDao.getEntityForDownloadId(downloadId) + val uri = ContentUris.withAppendedId(downloadBaseUri, downloadId) context.contentResolver - .update( - uri, - contentValues, - "title=?", - arrayOf(downloadEntity?.title) - ) + .update(uri, contentValues, null, null) true } catch (ignore: Exception) { Log.e("DOWNLOAD_MONITOR", "Couldn't pause/resume the download. Original exception = $ignore") diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index e0a9e91082..85cc9ec160 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -88,8 +88,7 @@ class DownloadManagerRequester @Inject constructor( } fun DownloadRequest.toDownloadManagerRequest( - sharedPreferenceUtil: SharedPreferenceUtil, - bytesDownloaded: Long = ZERO.toLong() + sharedPreferenceUtil: SharedPreferenceUtil ) = DownloadManager.Request(uri).apply { setDestinationUri(Uri.fromFile(getDestinationFile(sharedPreferenceUtil))) @@ -101,7 +100,4 @@ fun DownloadRequest.toDownloadManagerRequest( ) setAllowedOverMetered(true) setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. - if (bytesDownloaded > 0) { - addRequestHeader("Range", "bytes=$bytesDownloaded-") - } } From 775fab14bcba278697b4bbbd2e4c1b234c3cf23f Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 2 Aug 2024 10:29:50 +0530 Subject: [PATCH 28/41] Fixed unit coverage --- .../kiwixmobile/custom/download/CustomDownloadViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt b/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt index 489aea6600..727f68ca1a 100644 --- a/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt +++ b/custom/src/test/java/org/kiwix/kiwixmobile/custom/download/CustomDownloadViewModelTest.kt @@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.custom.download import com.jraska.livedata.test -import com.tonyodev.fetch2.Error.NONE import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk @@ -29,6 +28,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error.NONE import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Failed From 88d828e5ce2fb29b090cac05688922f86c42b8fb Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 2 Aug 2024 11:54:48 +0530 Subject: [PATCH 29/41] Removed the fetch library and its code from project. * Fixed the Downloading for Authentication URL. --- buildSrc/src/main/kotlin/Libs.kt | 2 - buildSrc/src/main/kotlin/Versions.kt | 2 - .../kotlin/plugin/AllProjectConfigurer.kt | 1 - core/objectbox-models/default.json | 140 +++---------- core/objectbox-models/default.json.bak | 14 +- .../kiwixmobile/core/dao/FetchDownloadDao.kt | 107 ---------- .../core/dao/entities/FetchDownloadEntity.kt | 98 --------- .../core/di/components/CoreComponent.kt | 2 - .../core/di/modules/DatabaseModule.kt | 9 - .../core/di/modules/DownloaderModule.kt | 56 ----- .../DownloadManagerRequester.kt | 48 ++++- .../downloader/fetch/FetchDownloadMonitor.kt | 119 ----------- .../fetch/FetchDownloadNotificationManager.kt | 193 ------------------ .../fetch/FetchDownloadRequester.kt | 65 ------ 14 files changed, 64 insertions(+), 792 deletions(-) delete mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt delete mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt delete mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt delete mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt delete mode 100644 core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index e25fd2a7b7..a2d4223c26 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -329,8 +329,6 @@ object Libs { const val barista: String = "com.adevinta.android:barista:" + Versions.barista - const val fetch: String = "com.github.tonyofrancis:fetch:" + Versions.fetch - /** * https://github.com/ReactiveX/RxJava */ diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6fe4eb51d7..a10d79df3d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -94,8 +94,6 @@ object Versions { const val barista: String = "4.3.0" - const val fetch: String = "3.1.6" - const val rxjava: String = "2.2.21" const val webkit: String = "1.7.0" diff --git a/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt b/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt index ae6d84569b..c32211adc6 100644 --- a/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt +++ b/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt @@ -201,7 +201,6 @@ class AllProjectConfigurer { implementation(Libs.collection_ktx) implementation(Libs.butterknife) kapt(Libs.butterknife_compiler) - implementation(Libs.fetch) implementation(Libs.rxandroid) implementation(Libs.rxjava) implementation(Libs.preference_ktx) diff --git a/core/objectbox-models/default.json b/core/objectbox-models/default.json index 6a46ca54c7..9c418ee2c1 100644 --- a/core/objectbox-models/default.json +++ b/core/objectbox-models/default.json @@ -248,120 +248,6 @@ ], "relations": [] }, - { - "id": "8:8093454424037540087", - "lastPropertyId": "24:4272820830206771469", - "name": "FetchDownloadEntity", - "properties": [ - { - "id": "1:7366957113003324901", - "name": "id", - "type": 6, - "flags": 1 - }, - { - "id": "3:3174500111130052488", - "name": "bookId", - "type": 9 - }, - { - "id": "4:3949362784963767166", - "name": "title", - "type": 9 - }, - { - "id": "5:812546090900770347", - "name": "description", - "type": 9 - }, - { - "id": "6:3129463483413863468", - "name": "language", - "type": 9 - }, - { - "id": "7:3402286918039853548", - "name": "creator", - "type": 9 - }, - { - "id": "8:4732753967507809221", - "name": "publisher", - "type": 9 - }, - { - "id": "9:3239042532048399134", - "name": "date", - "type": 9 - }, - { - "id": "10:1136584919149973914", - "name": "url", - "type": 9 - }, - { - "id": "11:4252749008345744598", - "name": "articleCount", - "type": 9 - }, - { - "id": "12:8625493380854102341", - "name": "mediaCount", - "type": 9 - }, - { - "id": "13:2787210837560254021", - "name": "size", - "type": 9 - }, - { - "id": "14:2052022387195277817", - "name": "name", - "type": 9 - }, - { - "id": "15:1976493094677983679", - "name": "favIcon", - "type": 9 - }, - { - "id": "16:217454020763036675", - "name": "etaInMilliSeconds", - "type": 6 - }, - { - "id": "17:1136630637198901642", - "name": "bytesDownloaded", - "type": 6 - }, - { - "id": "18:8939019296899137627", - "name": "totalSizeOfDownload", - "type": 6 - }, - { - "id": "21:5555873126720275555", - "name": "file", - "type": 9 - }, - { - "id": "22:2724607601244650879", - "name": "downloadId", - "type": 6 - }, - { - "id": "23:5485468735259326535", - "name": "progress", - "type": 5 - }, - { - "id": "24:4272820830206771469", - "name": "tags", - "type": 9 - } - ], - "relations": [] - }, { "id": "10:3205842982118792800", "lastPropertyId": "9:5286545520416917562", @@ -416,7 +302,8 @@ "retiredEntityUids": [ 349148274283701276, 7257718270326155947, - 7394649290555378565 + 7394649290555378565, + 8093454424037540087 ], "retiredIndexUids": [ 1293695782925933448, @@ -470,7 +357,28 @@ 4335394620556092321, 1899740026144478138, 3378789699620971394, - 6867355950440828062 + 6867355950440828062, + 7366957113003324901, + 3174500111130052488, + 3949362784963767166, + 812546090900770347, + 3129463483413863468, + 3402286918039853548, + 4732753967507809221, + 3239042532048399134, + 1136584919149973914, + 4252749008345744598, + 8625493380854102341, + 2787210837560254021, + 2052022387195277817, + 1976493094677983679, + 217454020763036675, + 1136630637198901642, + 8939019296899137627, + 5555873126720275555, + 2724607601244650879, + 5485468735259326535, + 4272820830206771469 ], "retiredRelationUids": [], "version": 1 diff --git a/core/objectbox-models/default.json.bak b/core/objectbox-models/default.json.bak index 9f15e6f8cc..6a46ca54c7 100644 --- a/core/objectbox-models/default.json.bak +++ b/core/objectbox-models/default.json.bak @@ -339,16 +339,6 @@ "name": "totalSizeOfDownload", "type": 6 }, - { - "id": "19:3378789699620971394", - "name": "status", - "type": 5 - }, - { - "id": "20:6867355950440828062", - "name": "error", - "type": 5 - }, { "id": "21:5555873126720275555", "name": "file", @@ -478,7 +468,9 @@ 8819082642546094709, 7233601933599801875, 4335394620556092321, - 1899740026144478138 + 1899740026144478138, + 3378789699620971394, + 6867355950440828062 ], "retiredRelationUids": [], "version": 1 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt deleted file mode 100644 index d3cde25293..0000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/FetchDownloadDao.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.dao - -import io.objectbox.Box -import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity -import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book -import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil -import javax.inject.Inject - -class FetchDownloadDao @Inject constructor( - private val box: Box, - private val newBookDao: NewBookDao, - private val sharedPreferenceUtil: SharedPreferenceUtil -) { - - // fun downloads(): Flowable> = - // box.asFlowable() - // .distinctUntilChanged() - // .doOnNext(::moveCompletedToBooksOnDiskDao) - // .map { it.map(::DownloadModel) } - - // fun allDownloads() = Single.fromCallable { box.all.map(::DownloadModel) } - - // private fun moveCompletedToBooksOnDiskDao(downloadEntities: List) { - // downloadEntities.filter { it.status == COMPLETED }.takeIf { it.isNotEmpty() }?.let { - // box.store.callInTx { - // box.remove(it) - // newBookDao.insert(it.map(::BookOnDisk)) - // } - // } - // } - - // fun update(download: Download) { - // box.store.callInTx { - // getEntityFor(download.id)?.let { dbEntity -> - // dbEntity.updateWith(download) - // .takeIf { updatedEntity -> updatedEntity != dbEntity } - // ?.let(box::put) - // } - // } - // } - - // fun getEntityFor(downloadId: Int) = - // box.query { - // equal(FetchDownloadEntity_.downloadId, downloadId) - // }.find().getOrNull(0) - - // fun getEntityForFileName(fileName: String) = - // box.query { - // endsWith( - // FetchDownloadEntity_.file, fileName, - // QueryBuilder.StringOrder.CASE_INSENSITIVE - // ) - // }.findFirst() - - fun insert(downloadId: Long, book: Book, filePath: String?) { - box.put(FetchDownloadEntity(downloadId, book, filePath)) - } - - // fun delete(downloadId: Long) { - // // remove the previous file from storage since we have cancelled the download. - // getEntityFor(downloadId.toInt())?.file?.let { - // File(it).deleteFile() - // } - // box.query { - // equal(FetchDownloadEntity_.downloadId, downloadId) - // }.remove() - // } - - // fun addIfDoesNotExist( - // url: String, - // book: Book, - // downloadRequester: DownloadRequester - // ) { - // box.store.callInTx { - // if (doesNotAlreadyExist(book)) { - // val downloadRequest = DownloadRequest(url, book.title) - // insert( - // downloadRequester.enqueue(downloadRequest), - // book = book, - // filePath = downloadRequest.getDestinationFile(sharedPreferenceUtil).path - // ) - // } - // } - // } - - // private fun doesNotAlreadyExist(book: Book) = - // box.query { - // equal(FetchDownloadEntity_.bookId, book.id, QueryBuilder.StringOrder.CASE_INSENSITIVE) - // }.count() == 0L -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt deleted file mode 100644 index b0200b2c90..0000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/entities/FetchDownloadEntity.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.dao.entities - -import com.tonyodev.fetch2.Download -import io.objectbox.annotation.Entity -import io.objectbox.annotation.Id -import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book - -@Entity -data class FetchDownloadEntity( - @Id var id: Long = 0, - var downloadId: Long, - val file: String? = null, - val etaInMilliSeconds: Long = -1L, - val bytesDownloaded: Long = -1L, - val totalSizeOfDownload: Long = -1L, - // @Convert(converter = StatusConverter::class, dbType = Int::class) - // val status: Status = Status.NONE, - // @Convert(converter = ErrorConverter::class, dbType = Int::class) - // val error: Error = Error.NONE, - val progress: Int = -1, - val bookId: String, - val title: String, - val description: String?, - val language: String, - val creator: String, - val publisher: String, - val date: String, - val url: String?, - val articleCount: String?, - val mediaCount: String?, - val size: String, - val name: String?, - val favIcon: String, - val tags: String? = null -) { - constructor(downloadId: Long, book: Book, file: String?) : this( - file = file, - downloadId = downloadId, - bookId = book.id, - title = book.title, - description = book.description, - language = book.language, - creator = book.creator, - publisher = book.publisher, - date = book.date, - url = book.url, - articleCount = book.articleCount, - mediaCount = book.mediaCount, - size = book.size, - name = book.bookName, - favIcon = book.favicon, - tags = book.tags - ) - - fun toBook() = Book().apply { - id = bookId - title = this@FetchDownloadEntity.title - description = this@FetchDownloadEntity.description - language = this@FetchDownloadEntity.language - creator = this@FetchDownloadEntity.creator - publisher = this@FetchDownloadEntity.publisher - date = this@FetchDownloadEntity.date - url = this@FetchDownloadEntity.url - articleCount = this@FetchDownloadEntity.articleCount - mediaCount = this@FetchDownloadEntity.mediaCount - size = this@FetchDownloadEntity.size - bookName = name - favicon = favIcon - tags = this@FetchDownloadEntity.tags - } - - fun updateWith(download: Download) = copy( - file = download.file, - etaInMilliSeconds = download.etaInMilliSeconds, - bytesDownloaded = download.downloaded, - totalSizeOfDownload = download.total, - // status = download.status, - // error = download.error, - progress = download.progress - ) -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index d1617e1eea..faba5b03f1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.sync.Mutex import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.HistoryDao import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks @@ -97,7 +96,6 @@ interface CoreComponent { fun application(): Application fun bookUtils(): BookUtils fun dataSource(): DataSource - fun fetchDownloadDao(): FetchDownloadDao fun newBookDao(): NewBookDao fun historyDao(): HistoryDao fun noteDao(): NewNoteDao diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt index 75c44619f9..c7b374e5aa 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DatabaseModule.kt @@ -22,7 +22,6 @@ import dagger.Module import dagger.Provides import io.objectbox.BoxStore import io.objectbox.kotlin.boxFor -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.FlowBuilder import org.kiwix.kiwixmobile.core.dao.HistoryDao import org.kiwix.kiwixmobile.core.dao.NewBookDao @@ -32,7 +31,6 @@ import org.kiwix.kiwixmobile.core.dao.NewNoteDao import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao import org.kiwix.kiwixmobile.core.dao.entities.MyObjectBox import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase -import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import javax.inject.Singleton @Module @@ -70,13 +68,6 @@ open class DatabaseModule { flowBuilder: FlowBuilder ): NewRecentSearchDao = NewRecentSearchDao(boxStore.boxFor(), flowBuilder) - @Provides @Singleton fun providesFetchDownloadDao( - boxStore: BoxStore, - newBookDao: NewBookDao, - sharedPreferenceUtil: SharedPreferenceUtil - ): FetchDownloadDao = - FetchDownloadDao(boxStore.boxFor(), newBookDao, sharedPreferenceUtil) - @Singleton @Provides fun provideYourDatabase( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt index 52c53bff1c..a021134dc4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/DownloaderModule.kt @@ -20,18 +20,9 @@ package org.kiwix.kiwixmobile.core.di.modules import android.app.DownloadManager import android.app.NotificationManager import android.content.Context -import com.tonyodev.fetch2.Fetch -import com.tonyodev.fetch2.Fetch.Impl -import com.tonyodev.fetch2.FetchConfiguration -import com.tonyodev.fetch2.FetchNotificationManager -import com.tonyodev.fetch2okhttp.OkHttpDownloader import dagger.Module import dagger.Provides -import okhttp3.OkHttpClient -import org.kiwix.kiwixmobile.core.BuildConfig import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao -import org.kiwix.kiwixmobile.core.data.remote.BasicAuthInterceptor import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.Downloader @@ -41,11 +32,7 @@ import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerMoni import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadManagerRequester import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationActionsBroadcastReceiver import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager -import org.kiwix.kiwixmobile.core.downloader.fetch.FetchDownloadNotificationManager -import org.kiwix.kiwixmobile.core.utils.CONNECT_TIME_OUT -import org.kiwix.kiwixmobile.core.utils.READ_TIME_OUT import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil -import java.util.concurrent.TimeUnit import javax.inject.Singleton @Module @@ -60,49 +47,6 @@ object DownloaderModule { ): Downloader = DownloaderImpl(downloadRequester, downloadRoomDao, kiwixService, sharedPreferenceUtil) - // @Provides - // @Singleton - // fun providesDownloadRequester(fetch: Fetch, sharedPreferenceUtil: SharedPreferenceUtil): - // DownloadRequester = FetchDownloadRequester(fetch, sharedPreferenceUtil) - - @Provides - @Singleton - fun provideFetch(fetchConfiguration: FetchConfiguration): Fetch = - Fetch.getInstance(fetchConfiguration) - - @Provides - @Singleton - fun provideFetchConfiguration( - context: Context, - okHttpDownloader: OkHttpDownloader, - fetchNotificationManager: FetchNotificationManager - ): FetchConfiguration = - FetchConfiguration.Builder(context).apply { - setDownloadConcurrentLimit(5) - enableLogging(BuildConfig.DEBUG) - enableRetryOnNetworkGain(true) - setHttpDownloader(okHttpDownloader) - preAllocateFileOnCreation(false) - setNotificationManager(fetchNotificationManager) - }.build().also(Impl::setDefaultInstanceConfiguration) - - @Provides - @Singleton - fun provideOkHttpDownloader() = OkHttpDownloader( - OkHttpClient.Builder() - .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MINUTES) - .readTimeout(READ_TIME_OUT, TimeUnit.MINUTES) - .addInterceptor(BasicAuthInterceptor()) - .followRedirects(true) - .followSslRedirects(true) - .build() - ) - - @Provides - @Singleton - fun provideFetchDownloadNotificationManager(context: Context, fetchDownloadDao: FetchDownloadDao): - FetchNotificationManager = FetchDownloadNotificationManager(context, fetchDownloadDao) - @Provides @Singleton fun providesDownloadRequester( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 85cc9ec160..303a578aaa 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -22,9 +22,13 @@ import android.app.DownloadManager import android.app.DownloadManager.Request import android.app.DownloadManager.Request.VISIBILITY_HIDDEN import android.net.Uri +import androidx.core.net.toUri import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.kiwix.kiwixmobile.core.data.remote.isAuthenticationUrl +import org.kiwix.kiwixmobile.core.data.remote.removeAuthenticationFromUrl +import org.kiwix.kiwixmobile.core.data.remote.secretKey import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil @@ -89,15 +93,37 @@ class DownloadManagerRequester @Inject constructor( fun DownloadRequest.toDownloadManagerRequest( sharedPreferenceUtil: SharedPreferenceUtil -) = - DownloadManager.Request(uri).apply { - setDestinationUri(Uri.fromFile(getDestinationFile(sharedPreferenceUtil))) - setAllowedNetworkTypes( - if (sharedPreferenceUtil.prefWifiOnly) - Request.NETWORK_WIFI - else - Request.NETWORK_MOBILE - ) - setAllowedOverMetered(true) - setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. +): DownloadManager.Request { + return if (urlString.isAuthenticationUrl) { + // return the request with "Authorization" header if the url is a Authentication url. + DownloadManager.Request(urlString.removeAuthenticationFromUrl.toUri()).apply { + setDestinationUri(Uri.fromFile(getDestinationFile(sharedPreferenceUtil))) + setAllowedNetworkTypes( + if (sharedPreferenceUtil.prefWifiOnly) + Request.NETWORK_WIFI + else + Request.NETWORK_MOBILE + ) + setAllowedOverMetered(true) + setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. + val userNameAndPassword = System.getenv(urlString.secretKey) ?: "" + val userName = userNameAndPassword.substringBefore(":", "") + val password = userNameAndPassword.substringAfter(":", "") + val credentials = okhttp3.Credentials.basic(userName, password) + addRequestHeader("Authorization", credentials) + } + } else { + // return the request for normal urls. + DownloadManager.Request(uri).apply { + setDestinationUri(Uri.fromFile(getDestinationFile(sharedPreferenceUtil))) + setAllowedNetworkTypes( + if (sharedPreferenceUtil.prefWifiOnly) + Request.NETWORK_WIFI + else + Request.NETWORK_MOBILE + ) + setAllowedOverMetered(true) + setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. + } } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt deleted file mode 100644 index 59c7bb9aef..0000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadMonitor.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.downloader.fetch - -import android.annotation.SuppressLint -import com.tonyodev.fetch2.Download -import com.tonyodev.fetch2.Error -import com.tonyodev.fetch2.Fetch -import com.tonyodev.fetch2.FetchListener -import com.tonyodev.fetch2core.DownloadBlock -import io.reactivex.schedulers.Schedulers -import io.reactivex.subjects.PublishSubject -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao -import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor -import javax.inject.Inject - -@SuppressLint("CheckResult") -class FetchDownloadMonitor @Inject constructor(fetch: Fetch, fetchDownloadDao: FetchDownloadDao) : - DownloadMonitor { - private val updater = PublishSubject.create<() -> Unit>() - private val fetchListener = object : FetchListener { - override fun onAdded(download: Download) {} - - override fun onCancelled(download: Download) { - delete(download) - } - - override fun onCompleted(download: Download) { - update(download) - } - - override fun onDeleted(download: Download) { - delete(download) - } - - override fun onDownloadBlockUpdated( - download: Download, - downloadBlock: DownloadBlock, - totalBlocks: Int - ) { - update(download) - } - - override fun onError(download: Download, error: Error, throwable: Throwable?) { - update(download) - } - - override fun onPaused(download: Download) { - update(download) - } - - override fun onProgress( - download: Download, - etaInMilliSeconds: Long, - downloadedBytesPerSecond: Long - ) { - update(download) - } - - override fun onQueued(download: Download, waitingOnNetwork: Boolean) { - update(download) - } - - override fun onRemoved(download: Download) { - delete(download) - } - - override fun onResumed(download: Download) { - update(download) - } - - override fun onStarted( - download: Download, - downloadBlocks: List, - totalBlocks: Int - ) { - update(download) - } - - override fun onWaitingNetwork(download: Download) { - update(download) - } - - private fun update(download: Download) { - // updater.onNext { fetchDownloadDao.update(download) } - } - - private fun delete(download: Download) { - // updater.onNext { download.delete(download.id.toLong()) } - } - } - - init { - fetch.addListener(fetchListener, true) - updater.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe( - { it.invoke() }, - Throwable::printStackTrace - ) - } - - override fun init() { - // empty method to so class does not get reported unused - } -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt deleted file mode 100644 index 9cdfc3254a..0000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadNotificationManager.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.downloader.fetch - -import android.annotation.SuppressLint -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_IMMUTABLE -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.app.PendingIntent.getActivity -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Build.VERSION_CODES -import androidx.annotation.RequiresApi -import androidx.core.app.NotificationCompat -import com.tonyodev.fetch2.ACTION_TYPE_CANCEL -import com.tonyodev.fetch2.ACTION_TYPE_DELETE -import com.tonyodev.fetch2.ACTION_TYPE_INVALID -import com.tonyodev.fetch2.ACTION_TYPE_PAUSE -import com.tonyodev.fetch2.ACTION_TYPE_RESUME -import com.tonyodev.fetch2.ACTION_TYPE_RETRY -import com.tonyodev.fetch2.DefaultFetchNotificationManager -import com.tonyodev.fetch2.DownloadNotification -import com.tonyodev.fetch2.EXTRA_ACTION_TYPE -import com.tonyodev.fetch2.EXTRA_DOWNLOAD_ID -import com.tonyodev.fetch2.EXTRA_GROUP_ACTION -import com.tonyodev.fetch2.EXTRA_NAMESPACE -import com.tonyodev.fetch2.EXTRA_NOTIFICATION_GROUP_ID -import com.tonyodev.fetch2.EXTRA_NOTIFICATION_ID -import com.tonyodev.fetch2.Fetch -import com.tonyodev.fetch2.util.DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET -import org.kiwix.kiwixmobile.core.Intents -import org.kiwix.kiwixmobile.core.R -import org.kiwix.kiwixmobile.core.R.string -import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao -import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE -import org.kiwix.kiwixmobile.core.main.CoreMainActivity - -class FetchDownloadNotificationManager( - private val context: Context, - private val fetchDownloadDao: FetchDownloadDao -) : - DefaultFetchNotificationManager(context) { - override fun getFetchInstanceForNamespace(namespace: String) = Fetch.getDefaultInstance() - - override fun createNotificationChannels( - context: Context, - notificationManager: NotificationManager - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelId = context.getString(R.string.fetch_notification_default_channel_id) - if (notificationManager.getNotificationChannel(channelId) == null) { - notificationManager.createNotificationChannel(createChannel(channelId, context)) - } - } - } - - override fun updateNotification( - notificationBuilder: NotificationCompat.Builder, - downloadNotification: DownloadNotification, - context: Context - ) { - // super method but with pause button removed - val smallIcon = if (downloadNotification.isDownloading) { - android.R.drawable.stat_sys_download - } else { - android.R.drawable.stat_sys_download_done - } - val notificationTitle = downloadNotification.title - notificationBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSmallIcon(smallIcon) - .setContentTitle(notificationTitle) - .setContentText(getSubtitleText(context, downloadNotification)) - .setOngoing(downloadNotification.isOnGoingNotification) - .setGroup(downloadNotification.groupId.toString()) - .setGroupSummary(false) - if (downloadNotification.isFailed || downloadNotification.isCompleted) { - notificationBuilder.setProgress(0, 0, false) - } else { - val progressIndeterminate = downloadNotification.progressIndeterminate - val maxProgress = if (downloadNotification.progressIndeterminate) 0 else 100 - val progress = if (downloadNotification.progress < 0) 0 else downloadNotification.progress - notificationBuilder.setProgress(maxProgress, progress, progressIndeterminate) - } - when { - downloadNotification.isDownloading -> - notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis()) - .addAction( - R.drawable.fetch_notification_cancel, - context.getString(R.string.cancel), - getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.DELETE) - ).addAction( - R.drawable.fetch_notification_pause, - context.getString(R.string.tts_pause), - getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.PAUSE) - ) - - downloadNotification.isPaused -> - notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis()) - .addAction( - R.drawable.fetch_notification_resume, - context.getString(R.string.tts_resume), - getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.RESUME) - ) - .addAction( - R.drawable.fetch_notification_cancel, - context.getString(R.string.cancel), - getActionPendingIntent(downloadNotification, DownloadNotification.ActionType.DELETE) - ) - - downloadNotification.isQueued -> - notificationBuilder.setTimeoutAfter(getNotificationTimeOutMillis()) - - else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) - } - notificationCustomisation(downloadNotification, notificationBuilder, context) - } - - override fun getActionPendingIntent( - downloadNotification: DownloadNotification, - actionType: DownloadNotification.ActionType - ): PendingIntent { - val intent = Intent(notificationManagerAction).apply { - putExtra(EXTRA_NAMESPACE, downloadNotification.namespace) - putExtra(EXTRA_DOWNLOAD_ID, downloadNotification.notificationId) - putExtra(EXTRA_NOTIFICATION_ID, downloadNotification.notificationId) - putExtra(EXTRA_GROUP_ACTION, false) - putExtra(EXTRA_NOTIFICATION_GROUP_ID, downloadNotification.groupId) - } - val action = when (actionType) { - DownloadNotification.ActionType.CANCEL -> ACTION_TYPE_CANCEL - DownloadNotification.ActionType.DELETE -> ACTION_TYPE_DELETE - DownloadNotification.ActionType.RESUME -> ACTION_TYPE_RESUME - DownloadNotification.ActionType.PAUSE -> ACTION_TYPE_PAUSE - DownloadNotification.ActionType.RETRY -> ACTION_TYPE_RETRY - else -> ACTION_TYPE_INVALID - } - intent.putExtra(EXTRA_ACTION_TYPE, action) - val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - return PendingIntent.getBroadcast( - context, - downloadNotification.notificationId + action, - intent, - flags - ) - } - - @SuppressLint("UnspecifiedImmutableFlag") - private fun notificationCustomisation( - downloadNotification: DownloadNotification, - notificationBuilder: NotificationCompat.Builder, - context: Context - ) { - if (downloadNotification.isCompleted) { - val internal = Intents.internal(CoreMainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - putExtra(DOWNLOAD_NOTIFICATION_TITLE, downloadNotification.title) - } - val pendingIntent = - getActivity(context, 0, internal, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT) - notificationBuilder.setContentIntent(pendingIntent) - notificationBuilder.setAutoCancel(true) - } - } - - @RequiresApi(VERSION_CODES.O) - private fun createChannel(channelId: String, context: Context) = - NotificationChannel( - channelId, - context.getString(string.fetch_notification_default_channel_name), - NotificationManager.IMPORTANCE_DEFAULT - ).apply { - setSound(null, null) - enableVibration(false) - } -} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt deleted file mode 100644 index 6497a12105..0000000000 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/fetch/FetchDownloadRequester.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Kiwix Android - * Copyright (c) 2019 Kiwix - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package org.kiwix.kiwixmobile.core.downloader.fetch - -import com.tonyodev.fetch2.Fetch -import com.tonyodev.fetch2.NetworkType.ALL -import com.tonyodev.fetch2.NetworkType.WIFI_ONLY -import com.tonyodev.fetch2.Request -import org.kiwix.kiwixmobile.core.downloader.DownloadRequester -import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest -import org.kiwix.kiwixmobile.core.utils.AUTO_RETRY_MAX_ATTEMPTS -import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil -import javax.inject.Inject - -class FetchDownloadRequester @Inject constructor( - private val fetch: Fetch, - private val sharedPreferenceUtil: SharedPreferenceUtil -) : DownloadRequester { - - override fun enqueue(downloadRequest: DownloadRequest): Long { - val request = downloadRequest.toFetchRequest(sharedPreferenceUtil) - fetch.enqueue(request) - return request.id.toLong() - } - - override fun cancel(downloadId: Long) { - fetch.delete(downloadId.toInt()) - } - - override fun retryDownload(downloadId: Long) { - fetch.retry(downloadId.toInt()) - } - - override fun pauseResumeDownload(downloadId: Long, isPause: Boolean) { - if (isPause) - fetch.resume(downloadId.toInt()) - else - fetch.pause(downloadId.toInt()) - } - - override fun onDownloadAdded() { - // empty function - } -} - -private fun DownloadRequest.toFetchRequest(sharedPreferenceUtil: SharedPreferenceUtil) = - Request("$uri", getDestinationFile(sharedPreferenceUtil).path).apply { - networkType = if (sharedPreferenceUtil.prefWifiOnly) WIFI_ONLY else ALL - autoRetryMaxAttempts = AUTO_RETRY_MAX_ATTEMPTS - } From 623d65e06fd6167a910299e972b32b7d0a518ae6 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 2 Aug 2024 12:30:39 +0530 Subject: [PATCH 30/41] Fixed sometimes downloads remain on the "Download Screen" after successfully downloading. * Improved the "DownloadTest". --- .../org/kiwix/kiwixmobile/download/DownloadRobot.kt | 10 ++++++++-- .../downloadManager/DownloadManagerMonitor.kt | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadRobot.kt index 7d62a3696c..2e4af24148 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/download/DownloadRobot.kt @@ -119,8 +119,14 @@ class DownloadRobot : BaseRobot() { } fun assertDownloadPaused() { - pauseForBetterTestPerformance() - onView(withText(org.kiwix.kiwixmobile.core.R.string.paused_state)).check(matches(isDisplayed())) + testFlakyView({ + pauseForBetterTestPerformance() + onView( + withText( + org.kiwix.kiwixmobile.core.R.string.paused_state + ) + ).check(matches(isDisplayed())) + }) } fun resumeDownload() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index ee608892ff..a602397cf4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -143,7 +143,8 @@ class DownloadManagerMonitor @Inject constructor( val query = DownloadManager.Query().setFilterByStatus( DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_PAUSED or - DownloadManager.STATUS_PENDING + DownloadManager.STATUS_PENDING or + DownloadManager.STATUS_SUCCESSFUL ) downloadManager.query(query).use { cursor -> if (cursor.moveToFirst()) { From 68b091ea6dced7c10d5a47e8f17e5f3348807076 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 2 Aug 2024 12:41:12 +0530 Subject: [PATCH 31/41] Enabled the controls in the notification for pausing/canceling the downloads when the download is in a Queued state. --- .../downloadManager/DownloadNotificationManager.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 8b08baba88..3c4379d617 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -77,7 +77,7 @@ class DownloadNotificationManager @Inject constructor( notificationBuilder.setProgress(HUNDERED, downloadNotificationModel.progress, false) } when { - downloadNotificationModel.isDownloading -> + downloadNotificationModel.isQueued || downloadNotificationModel.isDownloading -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) .addAction( R.drawable.ic_baseline_stop, @@ -101,9 +101,6 @@ class DownloadNotificationManager @Inject constructor( getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) ) - downloadNotificationModel.isQueued -> - notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) - else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) } notificationCustomisation(downloadNotificationModel, notificationBuilder, context) From 3e9ff05bd9fbdaa8b961b23b48497b893e5d1822 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Fri, 2 Aug 2024 18:16:25 +0530 Subject: [PATCH 32/41] Disabled the controls in the notification when the download is in a paused state, as the pause state could occur if DownloadManager is waiting for WiFi. * Fixed the user behavior when attempting to download the ZIM file only on WiFi, and added a proper message explaining why the download is paused. --- .../libraryView/adapter/LibraryViewHolder.kt | 21 ++++++++++-- .../downloadManager/DownloadManagerMonitor.kt | 30 ++++++++++++----- .../DownloadManagerRequester.kt | 8 ++--- .../DownloadNotificationManager.kt | 5 ++- .../core/downloader/downloadManager/Error.kt | 10 +++++- .../core/downloader/model/DownloadItem.kt | 33 +++++++++++++------ 6 files changed, 81 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 17ac0c2407..30c740bd06 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -97,6 +97,7 @@ sealed class LibraryViewHolder(containerView: View) : ) : LibraryViewHolder(itemDownloadBinding.root) { + @Suppress("MagicNumber") override fun bind(item: LibraryDownloadItem) { itemDownloadBinding.libraryDownloadFavicon.setBitmap(item.favIcon) itemDownloadBinding.libraryDownloadTitle.text = item.title @@ -118,12 +119,28 @@ sealed class LibraryViewHolder(containerView: View) : itemDownloadBinding.downloadState.text = item.downloadState.toReadableState(containerView.context).also { val pauseResumeIconId = - if (it == itemDownloadBinding.root.context.getString(R.string.paused_state)) { + if (it.contains(itemDownloadBinding.root.context.getString(R.string.paused_state))) { R.drawable.ic_play_24dp } else { R.drawable.ic_pause_24dp } - itemDownloadBinding.pauseResume.setImageDrawableCompat(pauseResumeIconId) + itemDownloadBinding.pauseResume.apply { + setImageDrawableCompat(pauseResumeIconId) + if (it == itemDownloadBinding.root.context.getString(R.string.paused_state) || + !it.contains(itemDownloadBinding.root.context.getString(R.string.paused_state)) + ) { + // If the download is paused by the user or is currently running, + // enable the pause/resume button. + isEnabled = true + alpha = 1f + } else { + // Otherwise, disable the pause/resume button because the download could be paused + // due to waiting for a WiFi connection if the user tries to download + // the ZIM files over WiFi only. + isEnabled = false + alpha = 0.5f + } + } } if (item.currentDownloadState == Status.FAILED) { clickAction.invoke(item) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index a602397cf4..630387e57e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -104,10 +104,6 @@ class DownloadManagerMonitor @Inject constructor( { try { synchronized(lock) { - Log.i( - "DOWNLOAD_MONITOR", - "Couldn't ${downloadRoomDao.downloads().blockingFirst()}" - ) if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { checkDownloads() } else { @@ -195,8 +191,14 @@ class DownloadManagerMonitor @Inject constructor( downloadId, progress, bytesDownloaded, - totalBytes - ) + totalBytes, + reason + ).also { + Log.e( + "UPDATE_STATUS", + "handlePausedDownload: $status, reason $reason" + ) + } DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadId) DownloadManager.STATUS_RUNNING -> handleRunningDownload( @@ -248,12 +250,14 @@ class DownloadManagerMonitor @Inject constructor( downloadId: Long, progress: Int, bytesDownloaded: Int, - totalSizeOfDownload: Int + totalSizeOfDownload: Int, + reason: Int ) { + val pauseReason = mapDownloadPauseReason(reason) updateDownloadStatus( downloadId = downloadId, status = Status.PAUSED, - error = Error.NONE, + error = pauseReason, progress = progress, bytesDownloaded = bytesDownloaded, totalSizeOfDownload = totalSizeOfDownload @@ -343,6 +347,16 @@ class DownloadManagerMonitor @Inject constructor( } } + private fun mapDownloadPauseReason(reason: Int): Error { + return when (reason) { + DownloadManager.PAUSED_QUEUED_FOR_WIFI -> Error.QUEUED_FOR_WIFI + DownloadManager.PAUSED_WAITING_TO_RETRY -> Error.WAITING_TO_RETRY + DownloadManager.PAUSED_WAITING_FOR_NETWORK -> Error.WAITING_FOR_NETWORK + DownloadManager.PAUSED_UNKNOWN -> Error.PAUSED_UNKNOWN + else -> Error.PAUSED_UNKNOWN + } + } + @Suppress("LongParameterList") private fun updateDownloadStatus( downloadId: Long, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt index 303a578aaa..25f9e6685e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerRequester.kt @@ -102,9 +102,9 @@ fun DownloadRequest.toDownloadManagerRequest( if (sharedPreferenceUtil.prefWifiOnly) Request.NETWORK_WIFI else - Request.NETWORK_MOBILE + Request.NETWORK_MOBILE or Request.NETWORK_WIFI ) - setAllowedOverMetered(true) + setAllowedOverMetered(!sharedPreferenceUtil.prefWifiOnly) setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. val userNameAndPassword = System.getenv(urlString.secretKey) ?: "" val userName = userNameAndPassword.substringBefore(":", "") @@ -120,9 +120,9 @@ fun DownloadRequest.toDownloadManagerRequest( if (sharedPreferenceUtil.prefWifiOnly) Request.NETWORK_WIFI else - Request.NETWORK_MOBILE + Request.NETWORK_MOBILE or Request.NETWORK_WIFI ) - setAllowedOverMetered(true) + setAllowedOverMetered(!sharedPreferenceUtil.prefWifiOnly) setNotificationVisibility(VISIBILITY_HIDDEN) // hide the default notification. } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt index 3c4379d617..8b08baba88 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadNotificationManager.kt @@ -77,7 +77,7 @@ class DownloadNotificationManager @Inject constructor( notificationBuilder.setProgress(HUNDERED, downloadNotificationModel.progress, false) } when { - downloadNotificationModel.isQueued || downloadNotificationModel.isDownloading -> + downloadNotificationModel.isDownloading -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) .addAction( R.drawable.ic_baseline_stop, @@ -101,6 +101,9 @@ class DownloadNotificationManager @Inject constructor( getActionPendingIntent(ACTION_RESUME, downloadNotificationModel.downloadId) ) + downloadNotificationModel.isQueued -> + notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER) + else -> notificationBuilder.setTimeoutAfter(DEFAULT_NOTIFICATION_TIMEOUT_AFTER_RESET) } notificationCustomisation(downloadNotificationModel, notificationBuilder, context) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt index f165738714..d6f22ad2ac 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/Error.kt @@ -33,7 +33,11 @@ enum class Error(val value: Int) { ERROR_HTTP_DATA_ERROR(9), ERROR_INSUFFICIENT_SPACE(10), ERROR_TOO_MANY_REDIRECTS(11), - ERROR_UNHANDLED_HTTP_CODE(12); + ERROR_UNHANDLED_HTTP_CODE(12), + QUEUED_FOR_WIFI(13), + WAITING_FOR_NETWORK(14), + WAITING_TO_RETRY(15), + PAUSED_UNKNOWN(16); companion object { @Suppress("ComplexMethod", "MagicNumber") @@ -54,6 +58,10 @@ enum class Error(val value: Int) { 10 -> ERROR_INSUFFICIENT_SPACE 11 -> ERROR_TOO_MANY_REDIRECTS 12 -> ERROR_UNHANDLED_HTTP_CODE + 13 -> QUEUED_FOR_WIFI + 14 -> WAITING_FOR_NETWORK + 15 -> WAITING_TO_RETRY + 16 -> PAUSED_UNKNOWN else -> UNKNOWN } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt index 98f66c1aba..a6f8d6449b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt @@ -19,18 +19,18 @@ package org.kiwix.kiwixmobile.core.downloader.model import android.content.Context import org.kiwix.kiwixmobile.core.R +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.NONE import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.ADDED -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.QUEUED -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.DOWNLOADING -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.PAUSED -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.COMPLETED import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.CANCELLED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.COMPLETED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.DELETED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.DOWNLOADING import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.FAILED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.NONE +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.PAUSED +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.QUEUED import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.REMOVED -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status.DELETED -import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error data class DownloadItem( val downloadId: Long, @@ -76,7 +76,7 @@ sealed class DownloadState( QUEUED -> Pending DOWNLOADING -> Running - PAUSED -> Paused + PAUSED -> Paused(error) COMPLETED -> Successful CANCELLED, FAILED, @@ -88,7 +88,7 @@ sealed class DownloadState( object Pending : DownloadState(R.string.pending_state) object Running : DownloadState(R.string.running_state) object Successful : DownloadState(R.string.complete) - object Paused : DownloadState(R.string.paused_state) + data class Paused(val reason: Error) : DownloadState(R.string.paused_state) data class Failed(val reason: Error, override val zimUrl: String?) : DownloadState(R.string.failed_state, zimUrl) @@ -96,9 +96,22 @@ sealed class DownloadState( fun toReadableState(context: Context): CharSequence = when (this) { is Failed -> context.getString(stringId, reason.name) + is Paused -> getPauseReasonText(context, stringId, reason) Pending, Running, - Paused, Successful -> context.getString(stringId) } + + private fun getPauseReasonText( + context: Context, + stringId: Int, + pauseError: Error + ): CharSequence { + return when (pauseError) { + Error.QUEUED_FOR_WIFI, + Error.WAITING_FOR_NETWORK -> "${context.getString(stringId)}: ${pauseError.name}" + + else -> context.getString(stringId) + } + } } From 2c8a9a45f82aca0a07d90fc9a2795a711fe80f09 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 6 Aug 2024 17:52:45 +0530 Subject: [PATCH 33/41] Fixed Downloading is not working on Android 11, and Android 12. * For Android 11 and above, we now download ZIM files to the app-specific directory because the download manager does not allow downloads to custom paths. Therefore, ZIM files are now downloaded inside the app-specific directory for these versions. * Removed the folder selection dialog from the application for selecting the SD card in non-Play Store variants on Android 11 and Android 12. * For Android 10 and below, ZIM files are now downloading to the public directory on the SD card. This ensures that the files are not lost when the application is uninstalled, as they are stored in a public directory similar to internal storage on these versions. * Also improved the showing pause reason on download screen when downloadManager is paused due to "PAUSED_WAITING_TO_RETRY" error so that user can know about the downloading state. --- .../library/OnlineLibraryFragment.kt | 64 ++-------------- .../core/downloader/model/DownloadItem.kt | 1 + .../core/settings/CorePrefsFragment.kt | 69 +++--------------- .../kiwix/kiwixmobile/core/utils/Constants.kt | 1 - .../core/utils/SharedPreferenceUtil.kt | 7 +- .../core/utils/dialog/KiwixDialog.kt | 8 -- .../select_folder_preview_image1.png | Bin 4012 -> 0 bytes .../select_folder_preview_image2.png | Bin 3060 -> 0 bytes .../select_folder_preview_image1.png | Bin 1794 -> 0 bytes .../select_folder_preview_image2.png | Bin 1338 -> 0 bytes .../select_folder_preview_image1.png | Bin 2471 -> 0 bytes .../select_folder_preview_image2.png | Bin 1880 -> 0 bytes .../select_folder_preview_image1.png | Bin 5865 -> 0 bytes .../select_folder_preview_image2.png | Bin 4267 -> 0 bytes .../select_folder_preview_image1.png | Bin 9815 -> 0 bytes .../select_folder_preview_image2.png | Bin 7230 -> 0 bytes .../select_folder_preview_image1.png | Bin 14241 -> 0 bytes .../select_folder_preview_image2.png | Bin 10433 -> 0 bytes .../main/res/layout/select_folder_dialog.xml | 45 ------------ core/src/main/res/values-ar/strings.xml | 1 - core/src/main/res/values-cs/strings.xml | 1 - core/src/main/res/values-dag/strings.xml | 1 - core/src/main/res/values-de/strings.xml | 1 - core/src/main/res/values-es/strings.xml | 1 - core/src/main/res/values-fr/strings.xml | 1 - core/src/main/res/values-ha/strings.xml | 1 - core/src/main/res/values-hi/strings.xml | 1 - core/src/main/res/values-ia/strings.xml | 1 - core/src/main/res/values-ig/strings.xml | 1 - core/src/main/res/values-it/strings.xml | 1 - core/src/main/res/values-iw/strings.xml | 1 - core/src/main/res/values-ko/strings.xml | 1 - core/src/main/res/values-ku/strings.xml | 1 - core/src/main/res/values-mk/strings.xml | 1 - core/src/main/res/values-nb/strings.xml | 1 - core/src/main/res/values-nl/strings.xml | 1 - core/src/main/res/values-nqo/strings.xml | 1 - core/src/main/res/values-or/strings.xml | 1 - core/src/main/res/values-pl/strings.xml | 1 - core/src/main/res/values-pt-rBR/strings.xml | 1 - core/src/main/res/values-qq/strings.xml | 1 - core/src/main/res/values-ru/strings.xml | 1 - core/src/main/res/values-sc/strings.xml | 1 - core/src/main/res/values-sk/strings.xml | 1 - core/src/main/res/values-sl/strings.xml | 1 - core/src/main/res/values-sq/strings.xml | 1 - core/src/main/res/values-sv/strings.xml | 1 - core/src/main/res/values-sw/strings.xml | 1 - core/src/main/res/values-ta/strings.xml | 1 - core/src/main/res/values-te/strings.xml | 1 - core/src/main/res/values-tn/strings.xml | 1 - core/src/main/res/values-tr/strings.xml | 1 - core/src/main/res/values-yo/strings.xml | 1 - core/src/main/res/values-zh-rTW/strings.xml | 1 - core/src/main/res/values-zh/strings.xml | 1 - core/src/main/res/values/strings.xml | 1 - 56 files changed, 18 insertions(+), 214 deletions(-) delete mode 100644 core/src/main/res/drawable-hdpi/select_folder_preview_image1.png delete mode 100644 core/src/main/res/drawable-hdpi/select_folder_preview_image2.png delete mode 100644 core/src/main/res/drawable-ldpi/select_folder_preview_image1.png delete mode 100644 core/src/main/res/drawable-ldpi/select_folder_preview_image2.png delete mode 100644 core/src/main/res/drawable-mdpi/select_folder_preview_image1.png delete mode 100644 core/src/main/res/drawable-mdpi/select_folder_preview_image2.png delete mode 100644 core/src/main/res/drawable-xhdpi/select_folder_preview_image1.png delete mode 100644 core/src/main/res/drawable-xhdpi/select_folder_preview_image2.png delete mode 100644 core/src/main/res/drawable-xxhdpi/select_folder_preview_image1.png delete mode 100644 core/src/main/res/drawable-xxhdpi/select_folder_preview_image2.png delete mode 100644 core/src/main/res/drawable-xxxhdpi/select_folder_preview_image1.png delete mode 100644 core/src/main/res/drawable-xxxhdpi/select_folder_preview_image2.png delete mode 100644 core/src/main/res/layout/select_folder_dialog.xml diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index d4757b6364..a190743640 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -22,7 +22,6 @@ import android.Manifest import android.Manifest.permission.POST_NOTIFICATIONS import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.annotation.SuppressLint -import android.app.Activity.RESULT_OK import android.content.Intent import android.content.pm.PackageManager.PERMISSION_GRANTED import android.net.ConnectivityManager @@ -37,7 +36,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar @@ -86,9 +84,7 @@ import org.kiwix.kiwixmobile.core.utils.SimpleTextListener import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog -import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.SelectFolder import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.YesNoDialog.WifiOnly -import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getPathFromUri import org.kiwix.kiwixmobile.databinding.FragmentDestinationDownloadBinding import org.kiwix.kiwixmobile.zimManager.NetworkState import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel @@ -382,62 +378,16 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { private fun storeDeviceInPreferences( storageDevice: StorageDevice ) { - if (storageDevice.isInternal) { - sharedPreferenceUtil.putPrefStorage( - sharedPreferenceUtil.getPublicDirectoryPath(storageDevice.name) - ) - sharedPreferenceUtil.putStoragePosition(INTERNAL_SELECT_POSITION) - clickOnBookItem() - } else { - if (sharedPreferenceUtil.isPlayStoreBuild) { - setExternalStoragePath(storageDevice) - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && - Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU - ) { - val view = LayoutInflater.from(activity).inflate(R.layout.select_folder_dialog, null) - dialogShower.show(SelectFolder { view }, ::selectFolder) - } else { - setExternalStoragePath(storageDevice) - } - } - } - } - - private fun setExternalStoragePath(storageDevice: StorageDevice) { - sharedPreferenceUtil.putPrefStorage(storageDevice.name) - sharedPreferenceUtil.putStoragePosition(EXTERNAL_SELECT_POSITION) - clickOnBookItem() - } - - private fun selectFolder() { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags( - Intent.FLAG_GRANT_READ_URI_PERMISSION - or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION + sharedPreferenceUtil.putPrefStorage( + sharedPreferenceUtil.getPublicDirectoryPath(storageDevice.name) ) - selectFolderLauncher.launch(intent) + sharedPreferenceUtil.putStoragePosition( + if (storageDevice.isInternal) INTERNAL_SELECT_POSITION + else EXTERNAL_SELECT_POSITION + ) + clickOnBookItem() } - private val selectFolderLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - result.data?.let { intent -> - getPathFromUri(requireActivity(), intent)?.let(sharedPreferenceUtil::putPrefStorage) - sharedPreferenceUtil.putStoragePosition(EXTERNAL_SELECT_POSITION) - clickOnBookItem() - } ?: run { - activity.toast( - resources - .getString(R.string.system_unable_to_grant_permission_message), - Toast.LENGTH_SHORT - ) - } - } - } - private fun requestNotificationPermission() { if (!shouldShowRationale(POST_NOTIFICATIONS)) { requireActivity().requestNotificationPermission() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt index a6f8d6449b..603a5052e3 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/model/DownloadItem.kt @@ -108,6 +108,7 @@ sealed class DownloadState( pauseError: Error ): CharSequence { return when (pauseError) { + Error.WAITING_TO_RETRY, Error.QUEUED_FOR_WIFI, Error.WAITING_FOR_NETWORK -> "${context.getString(stringId)}: ${pauseError.name}" diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt index fd7184778a..693184639a 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt @@ -59,8 +59,6 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.OpenCredits -import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.SelectFolder -import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getPathFromUri import java.io.File import java.io.InputStream import java.util.Locale @@ -446,68 +444,19 @@ abstract class CorePrefsFragment : @Suppress("NestedBlockDepth") fun onStorageDeviceSelected(storageDevice: StorageDevice) { sharedPreferenceUtil?.let { sharedPreferenceUtil -> - findPreference(SharedPreferenceUtil.PREF_STORAGE)?.summary = - storageCalculator?.calculateAvailableSpace(storageDevice.file) - if (storageDevice.isInternal) { - sharedPreferenceUtil.putPrefStorage( - sharedPreferenceUtil.getPublicDirectoryPath(storageDevice.name) - ) - sharedPreferenceUtil.putStoragePosition(INTERNAL_SELECT_POSITION) - setShowStorageOption() - setStorage() - } else { - if (sharedPreferenceUtil.isPlayStoreBuild) { - setExternalStoragePath(storageDevice) - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && - Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU - ) { - @SuppressLint("InflateParams") val view = LayoutInflater.from( - activity - ).inflate(R.layout.select_folder_dialog, null) - alertDialogShower?.show(SelectFolder { view }, ::selectFolder) - } else { - setExternalStoragePath(storageDevice) - } - } - } - } - return - } - - private fun setExternalStoragePath(storageDevice: StorageDevice) { - sharedPreferenceUtil?.putPrefStorage(storageDevice.name) - sharedPreferenceUtil?.putStoragePosition(EXTERNAL_SELECT_POSITION) - setShowStorageOption() - setStorage() - } - - private fun selectFolder() { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - addFlags( - Intent.FLAG_GRANT_READ_URI_PERMISSION - or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION + sharedPreferenceUtil.putPrefStorage( + sharedPreferenceUtil.getPublicDirectoryPath(storageDevice.name) + ) + sharedPreferenceUtil.putStoragePosition( + if (storageDevice.isInternal) INTERNAL_SELECT_POSITION + else EXTERNAL_SELECT_POSITION ) + setShowStorageOption() + setStorage() } - selectFolderLauncher.launch(intent) + return } - private val selectFolderLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - result.data?.let { intent -> - getPathFromUri(requireActivity(), intent)?.let { path -> - sharedPreferenceUtil?.putPrefStorage(path) - sharedPreferenceUtil?.putStoragePosition(EXTERNAL_SELECT_POSITION) - setShowStorageOption() - setStorage() - } - } - } - } - private fun setShowStorageOption() { sharedPreferenceUtil?.showStorageOption = false } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt index 024c65bc8a..3643edcd2f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/Constants.kt @@ -23,7 +23,6 @@ const val CONTACT_EMAIL_ADDRESS = "android@kiwix.org" // Request stuff const val REQUEST_STORAGE_PERMISSION = 1 const val REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE = 3 -const val REQUEST_SELECT_FOLDER_PERMISSION = 4 const val REQUEST_POST_NOTIFICATION_PERMISSION = 4 // Tags diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt index b9a9cc302f..0c41d7c4d7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt @@ -264,12 +264,7 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { } fun getPublicDirectoryPath(path: String): String = - if (isPlayStoreBuild) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) - path - else - path.substringBefore(context.getString(R.string.android_directory_seperator)) - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { path } else { path.substringBefore(context.getString(R.string.android_directory_seperator)) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt index 3264eec6df..470c7002e1 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt @@ -235,14 +235,6 @@ sealed class KiwixDialog( getView = customGetView ) - data class SelectFolder(val customGetView: (() -> View)?) : KiwixDialog( - R.string.select_folder, - null, - android.R.string.ok, - null, - getView = customGetView - ) - data class StartServer(val customGetView: (() -> View)?) : KiwixDialog( R.string.progress_dialog_starting_server, null, diff --git a/core/src/main/res/drawable-hdpi/select_folder_preview_image1.png b/core/src/main/res/drawable-hdpi/select_folder_preview_image1.png deleted file mode 100644 index d126231ddb0f2795550ce8c7421aef3b61e88d10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4012 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 kz82bV;@H2N{-0(1Ul6Nnf(I7g0MtfvU-HQ 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 diff --git a/core/src/main/res/drawable-mdpi/select_folder_preview_image2.png b/core/src/main/res/drawable-mdpi/select_folder_preview_image2.png deleted file mode 100644 index 84d1afef42ee0f54f133856a4573b80256393663..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1880 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 diff --git a/core/src/main/res/drawable-xhdpi/select_folder_preview_image2.png b/core/src/main/res/drawable-xhdpi/select_folder_preview_image2.png deleted file mode 100644 index 0ab78d226dc7fbdc5398845c433fc313c7efe2f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4267 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 diff --git a/core/src/main/res/drawable-xxhdpi/select_folder_preview_image2.png b/core/src/main/res/drawable-xxhdpi/select_folder_preview_image2.png deleted file mode 100644 index 5d664ef54469ab4172e7cd7c2cea2ac2290659a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7230 zcmZXZWn7c*+r}voancGRH9|m9222?x-6^0T&6JYv7@>5xfOI2>G^0Z)=~4mdaz7X_ zy5oQG^7%Y3fWfwV*L_{*c^u#4Buqn1{thWUDIOl)9YqBhP4Kf4d_|BDgYQp&dXVAa zG2Bp;k<|7`-^@Tc&>Yob#M`zqnXLAGSpp?QdGZjV5CTYPK>1yj&l?9u$;+KNRS)dr zzbUC`rd2KHFEyD}*SbGG><%Mz&2Un&TP%Ge9=eHebWpKrKcYguO%C+v6}>?M#h0S; zij*50*k7*mi)<8IDo*BQ+dtEGwel(~YAQHc7sR*^on){fD<=(1(1lix64@G&_zXRg zB&H-#wfpUU0;+p)SRXCYE)5uj5U;=L@*SSf2gaBlLy5Lb!9ooP{sdeVf1myXG2I8Y zK!y91%G?({QjyW0*b16A8lJj;d(8M%;ZA;{f1um5O|SWZPZUzw2lJWO#ylsr4%pMj zDmXnUk}EyE^-C)n+PL_vL<#~ZQIeTy>cj6Q6bg~iW!O_Ti`~{b&ta7QE_Ts@uE!CN zI9+}mZZj=MvS{4zn65mOr=S{%^&zCq!wP@<_RVIp;$uw>d}#;aKJO!V_wL=+dVFLh zU7^+KS@d!?R(QvQVI1zr(8FwqucxP{+vN5{{P;V0il}FmpV)-!RJ|jcs;Vko@b_lV zJ>6DsA$_!s=~R^&&E2~vJ%7<+?fV+}!~yhS3G(^FyV8l8xmNU>o0|+GZjooWwybHp zrlNiB?vi(51nq#+$08EHQ~z^-j> zuAX4BFq4&UMb9>>U0htYf86HX9umP-I4;Wxi->rxe$x3;QnJ*5$z-Dnt8m-YBV!U( zFgJe~rx0j-v9427SGTv@&s%LV%;e@!NY_EyZ)$pAI1#5=<+7&P*WX`hJ1u@$m}bW+ zC^!+355by?E_NJVlz z#9%&<@_pLAP1V))+3kBgCjSOLm%gbEuAB3^J&kU_mNoqx76aRZi<2x&wM&tJ zp1n=`bmjX^=6yq5=~hZ;g>Bm0oDn}izk?yI;l!wGh9x}QDz&~$88ij;LlqWI&K_p7 zi16^AeSOIOq$i+r_Kufh*aF)f8Pu{~3?_1Gkq`&$A0ARch)3a3r>9=vQYkP~f>Fyv zaNR(P6%2;0fc;(sIF&Lik}miPbw4;ql!p65;@C4)t!f@@t!cyL(<5j!9I+79EhHq= z`k^2Z5iJd)+ZUr)FQ@UFnVEqVu~X%s(MdO%Jn%YT;BGBby9Wj5BWfmW#s+!tpft{35LO-rZon!Zab+5XtZWSP znNS;i`H7v&JNV_O%heToavZosLiysX@~*nT*FOXPaBW{F+`5S2ytlO=TH&60HiSKWNiSpp>GBlDp_(Q%qMp43)`y+1J;H zXK7>8iM9omA?fTaP^lYPtW|W1tmWyi(-%v7b>h@Ru0vBPIGPH#qI#@TmfzBH5zgwy z5OT7Ii8=nNGY-!k^^W6S65dCA*aCjc&d3DP!6D*@A2qLh%#BsxB7T*IcF^&?<~*w9L<6! z+%_h^N8T4WUI-xn+1m>h5z)Wp!NAAWrn}p@^?ur6Mr~>6=U^7QE&g2OXRW%bD9lP(cxlP>W zt;n{bPcnlBDj3oHcM~eAPUM~HO+!MYJ%mG;c2YJCQw`TBWU_m`Sd0ju>5W+&Zx2R# z$2qYED|3eGkg6DLBD#*D921k=G}4k`x_tj$vc8V=ObosImWcz|mBLxjOx@R%P4`GD z^cQ6%g@0@7i&g9^2q`xQkSt4hsxa$e{D)?)bScMY%`?v2X6vDJ+qA)tXeNH>C*0yT zr~aCLv&%%$h_JYsI-Db$lDJH-U-2u$6~-1R%uTTjoRvU>d-DISkp9Vq<%=MY|pOM*Fu?1ILrBx~@>^Yw!!^XzO^SzD9%7A1e zuU_#sq>aEAlb+R*%gE<*1f#$rp4-otf?V08;59>~*hJEGC%s7ls z`gH0Y773XB&iSUMrWnM$5uPa$p{*>`FJS;co-QQn+?px``7+t=@hINIF)nhNKc%!C(l9ER+ zE|1?{rFe)A8M@DQ5t56-N^||^bWXnq+$=6FT@jf-A;WthXqR7D7;H6O2n_^25+cFU zUS-zrvD?czRcFuq`Sa&@@7`%>Xe=BZ>(<#Z#)$5cj2Eir0j=o>Ar}BnsFoqz72~_l zhL`lzutX*7L7?f;#&+QPZ%(6n2k889x1gNCPxT2`zeEUefUkuHC|B$CMm2SH@*R)# z=4Mfw>BAU42w{KK^?rg=A#A-5SIw)f{gj^0@H@sXQs9x8NWA{dNOO6wX|xY><%Brf z)Ya{<()MeKHa8_=puIEqA$~ct1d5m4}inrF#9=CB_$=mEsH;bZu98X{KWBL!fFZ& zyK&f;T3XUP_uc|(P{|N}6;;0+`t@+5f+qsV+E&Yv(ciy+%jz#xm3jfx32#ibVvr)v z{~+?w3^}Exku9jBvAH=lXXonqr9;3|MMXs+iU|*2>g)GsU7cIJE;mxu*MDGZYwJrp zx}uo9)Qw4Hb7Y}dh!)wZ{}(3rw_2J+QeOUMLqo$rmUtE*R;R5Qp{nX?ZeCu@#nF6G zOABLv)qm^jxeX1Ufpz6s(MK->!59Q(*W1^(d&d758be-o-|f)L&A?ij%sY}e$?T<( zk(#kFvwoc&$Gr$XvpyR0q4Zt=E`Ub*s{oyvobIp9_+4E9II8r!^5vLwlvCQ@_|Pe? z?B@2|(9jSx2u*(#Z~^n)cn(lcu(#b15K=(*7JxejRiLM@U&#{zxMV0(45dg~g!GQ3 z%BshlrUIi_bVy0yR0o}d1aacv;6NoE5eF7N{`apEf&nZ<2oKPUmbNxg`_^m|zHYrk z&Mtn8fM%IRh$&sG-y}#$r|upx_~OjEB#Y|8EgQmJQL0Ku8};=T)fU#MEfL;MNS54w zvmAWRF?>h1FYH*)2SGgDCe7!oP`s*0+U20}f6PJt+AkD^a7173{tUNXw(4}91tw89 zpm?yM0U@?`eW|Xlz8*TM+@sw<9{zpd8-5Ut04I?6yb7d-0{1BVj^Tusa%c92Y1e|n zpRipuu5MAK&i`i<+?-1*ujqSMzcyn&9Z5OzadRT4(Q8{MX zJ0eZKv;MVQ^X1F$YvV;z)fVLHqB}%VBwi@gNw2!^5BIh+!$*%EY15$?U|-@UBecj1 zo=wEs%Z&fGwN(h3)7}TG&-11#5Cp#QKYzM!^ES5w9rf5Bl`k$Yw>a9G1!_D7BA$*8 zZQ66&r3CdCk`u0On}56h4W@y+IK&w`z7;6VWSo1yGP*H^e+z{GzzjP6b*ZkTsVNjN z4qmBlr9U9=Opm2ob}a|5{XRLreti#g0OnfvV=a>%Fd6RAUVO9w0l$=_+$3Yzo+eEx z>;*`nsIJcJFr$ESV>iYv-5tslT6on9R~^TpX~;qv_J%|nfYN|y3k71QZ`UAOD?lf2 zP;DU(P`bSJ^ifMo%Vppi@Q;CXA?Nn^6Oh>+s-+`Xp{N!iylXuLCE5>eb`nHTu+AAz zOor&mN&;|1st%X~Lx?3@$_pdtx@{du9uM zSCEyx(d3SM2sX^$p8(IU?ZWNeQ-kZjUX!7P1^M!D4qZf~(r~mcPq+V#+j2A29S?rz zjdHE6tn24)ln&pT@>=Xco z^G63C=Bqng4d<$IEk1pw{P^jKFpj*niBm zpt!lXvYVUJ)nA+hR#ou>(?$TGuA6mBBNdEcD-)(d3Efdn;%NYyI#%6L?IR!{aP65H z?sNFrK$7?OT|i_2S(+vzBLkQ)zx5xUw%_;?fDV90xwyIU1MGN}<&nW`VSU%C3|A@psj?E--KmuU2MGO;Oi*d!pX;<8WZXWAOB2{H-_N=ACh|9bDyKm~K zk2ZWmoh%$0p~*>>301n zW)dPz`OfS=U9%qhewl8fITjN^CKML)S+SKcmCO*AwWHUN&1z z-3q}F?n>kbBxnB2k3xd24O0+CjyXuJO8#_i{jvt+VJNn4@AkHw|iPg=}y}e|8C6|kqRvn+1 z0o8z^nV?j?*&{hn&v?<+j7Q>typ>f!9dc{+R!)oqGFg-LP>;bQPQ<>pw$KV>Wflq+ zrWi0=1UW-N=EPuvaXY;v^z$S3W~$r@NO<2`(18-#t>baB}IK6 z(my|MKonV9>kGcOVg7xl0F{pO!J%ws8}D|&D5S0@b6^f54}7KV@}N9SE>HAgf1Kfk zcgoScUskR41XY-f$9i$mmv7(9&JH(4_XY$mcR6Yc+KCl%po)J3lRSKhFb6Qe9>J!^ zdGiPDO~sj+Oj0=%Aa*{z4)tJm38dzF!owOz;^X53LWG8nPS5)9uf4s!i+M3GkbD0I zK?YeQ_fr6r#oKny53&aUkgo=j4`{fl=h0xsIyy0NR9#b3(*x|Z9Q)p)d}s#%3kb+y zpfEN*F0HJ58*uf?>gwps%mA2uAsm;80LrVR!oy5p3UA-C;mOL%f?Ur+q2ab;8J9~4 z#xSbt%Hv>AopsJ}mSkqA17^)WB6QpROB({#a$wCawAwBzuLBacWq#}3Ds zLqQs(cqhbB&0eWxw4P>fYzw2U1Ex*`eq<198v!G(4|i&u84t7c0&jSK5#p zw^H#()i$17-Tr~0-l|GG)+cQbGP(Eh>fCxH>X%dH_k|>^@g`jt7YlTe$vOLk!sWSt zT(^e6;0x+}JdxmmpUQzXij~?bvL#R|HithlHCOxWtj+|8L)WT=vt;3eAjhj?%d1>1 zeEu$f#&8;$Miv*DmqM%-tOx_~JzYYt9~Vm(HQwbwS15kUwEu9Ew+-5@*pd{W^1Mfz z12Qy&6lCVq@r)CdO5wZxLuBSbRZAYnSk?nlD3uoc#L5l=Ne{KAy{a~Fu_Xr*6iPx& zz(RyJ%enp0oamf@d(4rNp!BknM@v8g%bUG~=rl|=X(z%*o4x%{&yPQr2-P4m!egDVHv%j-%OTu)C|}da0~tN{5T#5i%v|$ ztndkl#I$8vWlx}wk&4{4wzf6<2k~}v`%};F-jJoqFF46N_yl?$a-UXlgk*d8?CX^4 zCB&*x@>Rc(Qnk}DhOnu@Pfkb>vr>6sLufe4HO$j=TZ9SYr@hH8El@8c7{kNX-u65b zX{|+AShhE`mcWwMS#C9 zEaI_zOc`BMM@d1THBRlhlo}npP;Q}KeWJ}66$gXPvP?jzAgSK=f4uVpnWm)(UeEks zdKah>)fo>8j9=;ts2KjT#FR!g*+rR4 zT$~olaQ=!9o@W423m%9DqSXX%}V2=+)vKtM2SwM%Q#6a7)_8|FBq*zA1x zM%^AZ;8{J@RwjRdN&7pbUZ_ZT5law*OQJrA0q@|@3d4|3<9)|XiUr`DPu8F|J^!MY zdMC$ZSzxXiZM-eF^cyXuI)1^Iq5~piX#MvkVllh7D-0Fg^n}s2m0!N3$jWv}_dU}D zb&-~0X1XqH6)ZV`9tmt8aV22q_cG+>ogzOh`m4lZu219|0B1yAJnXy_})8VXIb^V#JBA5#M+ToZC6 zBzf| HnfU(?0~lEH diff --git a/core/src/main/res/drawable-xxxhdpi/select_folder_preview_image1.png b/core/src/main/res/drawable-xxxhdpi/select_folder_preview_image1.png deleted file mode 100644 index 50983145263bf1ac56daf590bc395435567353a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14241 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 diff --git a/core/src/main/res/layout/select_folder_dialog.xml b/core/src/main/res/layout/select_folder_dialog.xml deleted file mode 100644 index 4c44653229..0000000000 --- a/core/src/main/res/layout/select_folder_dialog.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - diff --git a/core/src/main/res/values-ar/strings.xml b/core/src/main/res/values-ar/strings.xml index 840ec88568..fadc2290b4 100644 --- a/core/src/main/res/values-ar/strings.xml +++ b/core/src/main/res/values-ar/strings.xml @@ -307,7 +307,6 @@ مسموح به غير مسموح به يرجى تمكين wifi لتنزيل المحتوى - الرجاء تحديد مجلد للتخزين الخارجي. النظام غير قادر على منح الإذن! السماح لا توجد ملاحظات diff --git a/core/src/main/res/values-cs/strings.xml b/core/src/main/res/values-cs/strings.xml index 620bd19de2..0a0afc38e3 100644 --- a/core/src/main/res/values-cs/strings.xml +++ b/core/src/main/res/values-cs/strings.xml @@ -300,7 +300,6 @@ Povoleno Není povoleno Pro stahování obsahu prosím povolte wifi - Vyberte složku pro externí úložiště. Systém nemůže udělit oprávnění! Povolit Žádné poznámky diff --git a/core/src/main/res/values-dag/strings.xml b/core/src/main/res/values-dag/strings.xml index 85776a1fa8..7bfd6e42a0 100644 --- a/core/src/main/res/values-dag/strings.xml +++ b/core/src/main/res/values-dag/strings.xml @@ -309,7 +309,6 @@ Soli beni Soli kani Pahimi suɣilo niŋmi Wifi din yɛn chɛ ka a tooi deei lahabali - Pahimi suɣilo piimi gbaŋ zaŋ n-ti sambani deei niŋ shee. Di puuni bi tooi ti soli! Saɣima Dihimi gbuni zaŋ n-ti Karimbu shee diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml index eae518945d..9606d81253 100644 --- a/core/src/main/res/values-de/strings.xml +++ b/core/src/main/res/values-de/strings.xml @@ -331,7 +331,6 @@ Erlaubt Nicht erlaubt Bitte WLAN aktivieren, um Inhalte herunterzuladen - Bitte einen Ordner für externe Speicherung auswählen. System kann keine Berechtigung erteilen! Erlauben Nach unten wischen für Bibliothek diff --git a/core/src/main/res/values-es/strings.xml b/core/src/main/res/values-es/strings.xml index 6338df4982..e14cfd4cba 100644 --- a/core/src/main/res/values-es/strings.xml +++ b/core/src/main/res/values-es/strings.xml @@ -326,7 +326,6 @@ Permitido No permitido Por favor habilite WiFi para descargar contenido - Por favor, seleccione una carpeta para el almacenamiento externo. Sistema incapaz de conceder permiso! Permitir Deslizar hacia abajo para abrir la Biblioteca diff --git a/core/src/main/res/values-fr/strings.xml b/core/src/main/res/values-fr/strings.xml index 551b755986..617770d486 100644 --- a/core/src/main/res/values-fr/strings.xml +++ b/core/src/main/res/values-fr/strings.xml @@ -342,7 +342,6 @@ Autorisé Non autorisé Veuillez activer le Wi-Fi pour télécharger le contenu - Veuillez sélectionner un dossier pour le stockage externe. Le système n’a pas pu accorder le droit d’accès ! Autoriser Balayez vers le bas pour la bibliothèque diff --git a/core/src/main/res/values-ha/strings.xml b/core/src/main/res/values-ha/strings.xml index 1940cb3f42..a496d91d9e 100644 --- a/core/src/main/res/values-ha/strings.xml +++ b/core/src/main/res/values-ha/strings.xml @@ -306,7 +306,6 @@ An ba da izini Ba a yarda ba Da fatan za a kunna wifi don zazzage abun ciki - Da fatan za a zaɓi babban fayil don ajiyar waje. Tsarin baya iya ba da izini! Izini Danna ƙasa don Laburare diff --git a/core/src/main/res/values-hi/strings.xml b/core/src/main/res/values-hi/strings.xml index 79d0684b8b..26a8a64c35 100644 --- a/core/src/main/res/values-hi/strings.xml +++ b/core/src/main/res/values-hi/strings.xml @@ -305,7 +305,6 @@ अनुमतित अनुमति नहीं है कृपया सामग्री डाउनलोड करने के लिए वाईफाई सक्षम करें - कृपया बाहरी भंडारण के लिए एक फ़ोल्डर चुनें। सिस्टम अनुमति देने में असमर्थ! अनुमति दें पुस्तकालय के लिए नीचे स्वाइप करें diff --git a/core/src/main/res/values-ia/strings.xml b/core/src/main/res/values-ia/strings.xml index 493b1691f4..de46a992a9 100644 --- a/core/src/main/res/values-ia/strings.xml +++ b/core/src/main/res/values-ia/strings.xml @@ -321,7 +321,6 @@ Permittite Non permittite Per favor activa Wi-Fi pro discargar contento - Per favor selige un dossier pro immagazinage externe. Systema incapace de conceder permission! Autorisar Glissa a basso pro le bibliotheca diff --git a/core/src/main/res/values-ig/strings.xml b/core/src/main/res/values-ig/strings.xml index 474838dcb9..8d20199fee 100644 --- a/core/src/main/res/values-ig/strings.xml +++ b/core/src/main/res/values-ig/strings.xml @@ -307,7 +307,6 @@ E nyere ikike Enyeghi ikike Biko mee ka wifi nwee ike ibudata ọdịnaya - Biko họrọ igbe nchekwa maka nchekwa mpụga. Sistemu enweghị ike inye ikike! Kwee Gbadaa ala maka ọba akwụkwọ diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml index 8a128d44b9..d044995439 100644 --- a/core/src/main/res/values-it/strings.xml +++ b/core/src/main/res/values-it/strings.xml @@ -310,7 +310,6 @@ Consentita Non consentita Abilita il Wi-Fi per scaricare i contenuti - Seleziona una cartella per l’archiviazione esterna. Il sistema non è in grado di concedere l’autorizzazione! Consenti Nessuna nota diff --git a/core/src/main/res/values-iw/strings.xml b/core/src/main/res/values-iw/strings.xml index 40e3a912da..840bbf90f0 100644 --- a/core/src/main/res/values-iw/strings.xml +++ b/core/src/main/res/values-iw/strings.xml @@ -328,7 +328,6 @@ מותר אסור נא להפעיל וייפיי כדי להוריד תוכן - נא לבחור תיקייה לאחסון חיצוני. המערכת אינה יכולה לתת הרשאה! לאפשר יש להחליק למטה עבור ספרייה diff --git a/core/src/main/res/values-ko/strings.xml b/core/src/main/res/values-ko/strings.xml index c867e52628..640a852064 100644 --- a/core/src/main/res/values-ko/strings.xml +++ b/core/src/main/res/values-ko/strings.xml @@ -312,7 +312,6 @@ 허용됨 허용하지 않음 내용을 다운로드하려면 와이파이를 활성화해 주십시오 - 외부 저장을 위한 폴더를 선택해 주십시오. 시스템이 권한을 부여할 수 없습니다! 허용 다운로드 버튼을 클릭해 주십시오. 그러면 필요한 언어를 자동으로 다운로드하게 됩니다. diff --git a/core/src/main/res/values-ku/strings.xml b/core/src/main/res/values-ku/strings.xml index 0b347dc8ef..7b0aa3b6d1 100644 --- a/core/src/main/res/values-ku/strings.xml +++ b/core/src/main/res/values-ku/strings.xml @@ -294,7 +294,6 @@ Destûrdayî Ne destûrdayî Ji bo daxistina naverokê ji kerema xwe wîfiyê aktîv bike - Ji kerema xwe peldankekê bibijêre ji bo depokirina xaricî. Sîstem nikare destûrê werbigire! Destûr bide Not tine diff --git a/core/src/main/res/values-mk/strings.xml b/core/src/main/res/values-mk/strings.xml index 97459b7160..b85eda7ddc 100644 --- a/core/src/main/res/values-mk/strings.xml +++ b/core/src/main/res/values-mk/strings.xml @@ -320,7 +320,6 @@ Дозволено Не е дозволено Вклучете ја безжичната мрежа (Wi-Fi) за да ја преземете содржината - Изберете ја папката за надворешно складирање. Системот не може да ја даде дозволата! Дозволи Повлечете надолу за Библиотеката diff --git a/core/src/main/res/values-nb/strings.xml b/core/src/main/res/values-nb/strings.xml index ebdc70033d..3c86616ce6 100644 --- a/core/src/main/res/values-nb/strings.xml +++ b/core/src/main/res/values-nb/strings.xml @@ -154,7 +154,6 @@ Tillatt Ikke tillatt Aktiver wifi for å laste ned innhold - Velg en mappe for ekstern lagring. Systemet kan ikke gi tillatelse! Tillat Ingen merknader diff --git a/core/src/main/res/values-nl/strings.xml b/core/src/main/res/values-nl/strings.xml index 49ba4e7d92..ceac944eb3 100644 --- a/core/src/main/res/values-nl/strings.xml +++ b/core/src/main/res/values-nl/strings.xml @@ -312,7 +312,6 @@ Toegestaan Niet toegestaan Schakel WiFi in om inhoud te downloaden - Kies een map voor externe opslag. Systeem kan geen toestemming verlenen! Toestaan Veeg omlaag voor bibliotheek diff --git a/core/src/main/res/values-nqo/strings.xml b/core/src/main/res/values-nqo/strings.xml index a8ed540967..5e44305702 100644 --- a/core/src/main/res/values-nqo/strings.xml +++ b/core/src/main/res/values-nqo/strings.xml @@ -295,7 +295,6 @@ ߟߊߘߌ߬ߢߍ߬ߣߍ߲ ߟߊߘߌ߬ߢߍ߬ߓߊߟߌ ߥߌߝߌ ߟߊߞߎߣߎ߲߫ ߖߊ߰ߣߌ߲߫ ߛߊ߫ ߞߊ߬ ߛߋ߫ ߞߣߐߘߐ ߓߍ߯ ߟߊߖߌ߰ ߟߴߌ ߞߎ߲߬ - ߞߎ߲ߓߍ߲ ߘߏ߫ ߛߎߥߊ߲ߘߌ߫ ߖߊ߰ߣߌ߲߫ ߞߣߐߘߐ ߟߊ߬ߡߙߊ߬ߦߙߐ߬ ߞߏ ߘߐ߫. ߘߌ߬ߢߍ߬ ߞߍߣߍ߲߫ ߣߍ߫ ߞߊ߲ߞߋ ߢߍ߫ ߖߡߊ߬ߙߌ߬ߟߌ ߘߐ߫߹ ߊ߬ ߟߊߘߌ߬ߢߍ߬ ߊ߬ ߕߏ߬ߙߏ߲߬ߘߏ߫ ߘߎ߰ߟߊ ߘߐ߫ ߛߓߍߘߊ ߢߍ߫ diff --git a/core/src/main/res/values-or/strings.xml b/core/src/main/res/values-or/strings.xml index 375632c2b5..5ff79e1adf 100644 --- a/core/src/main/res/values-or/strings.xml +++ b/core/src/main/res/values-or/strings.xml @@ -296,7 +296,6 @@ ଅନୁମତି ଦିଆଯାଇଛି ଅନୁମତି ନାହିଁ ବିଷୟବସ୍ତୁ ଡାଉନଲୋଡ୍ କରିବାକୁ ଦୟାକରି ୱାଇଫାଇ ସକ୍ଷମ କରନ୍ତୁ | - ବାହ୍ୟ ସଂରକ୍ଷଣ ପାଇଁ ଦୟାକରି ଏକ ଫୋଲ୍ଡର ବାଛନ୍ତୁ | ଅନୁମତି ପ୍ରଦାନ କରିବାରେ ଅସମର୍ଥ ସିଷ୍ଟମ୍ ଅନୁମତି ଦିଅନ୍ତୁ ଲାଇବ୍ରେରୀ ପାଇଁ ତଳକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ diff --git a/core/src/main/res/values-pl/strings.xml b/core/src/main/res/values-pl/strings.xml index eed110a48e..b3cb608ca0 100644 --- a/core/src/main/res/values-pl/strings.xml +++ b/core/src/main/res/values-pl/strings.xml @@ -313,7 +313,6 @@ Aby uzyskać dostęp do wszystkich plików zim na urządzeniu, musimy mieć uprawnienia do wszystkich plików Dozwolony Niedozwolony - Wybierz katalog do przechowywania zewnętrznego. System nie może udzielić pozwolenia! Przejdź do ustawień diff --git a/core/src/main/res/values-pt-rBR/strings.xml b/core/src/main/res/values-pt-rBR/strings.xml index ee5b6a4a36..24cc85bae1 100644 --- a/core/src/main/res/values-pt-rBR/strings.xml +++ b/core/src/main/res/values-pt-rBR/strings.xml @@ -320,7 +320,6 @@ Permitido Não permitido Por favor, habilite o wi-fi para o download de conteúdo - Por favor selecione uma pasta para o armazenamento externo. O sistema foi incapaz de garantir a permissão! Permitir Deslize para baixo para exibir a Biblioteca diff --git a/core/src/main/res/values-qq/strings.xml b/core/src/main/res/values-qq/strings.xml index 7cd8df3027..be0aa5fc32 100644 --- a/core/src/main/res/values-qq/strings.xml +++ b/core/src/main/res/values-qq/strings.xml @@ -305,7 +305,6 @@ This currently appears under the \"Allow to read and write ZIM files on SD card\" preference title This is used in the toast, to inform the user please enable their wifi to download the content. {{Ignored}} - This is used in the dialog as a title message, to tell the user, to select a folder to use the external storage. This is used in a toast to inform the user that the system is unable to grant permission. {{identical|Allow}} * It is text information shown on the download screen.\n* It is telling the user that they can load content by swiping down on the screen. diff --git a/core/src/main/res/values-ru/strings.xml b/core/src/main/res/values-ru/strings.xml index 878d42b365..2e7b535fb0 100644 --- a/core/src/main/res/values-ru/strings.xml +++ b/core/src/main/res/values-ru/strings.xml @@ -340,7 +340,6 @@ Разрешено Не разрешено Включите Wi-Fi для загрузки содержания - Выберите папку для внешнего хранилища. Система не может предоставить разрешение! Разрешить Проведите пальцем вниз, чтобы открыть библиотеку diff --git a/core/src/main/res/values-sc/strings.xml b/core/src/main/res/values-sc/strings.xml index 9b9a010254..fc8d266aa0 100644 --- a/core/src/main/res/values-sc/strings.xml +++ b/core/src/main/res/values-sc/strings.xml @@ -296,7 +296,6 @@ Permìtidu Non permìtidu Abìlita su wifi pro iscarrigare cuntenutos - Pro praghere seletziona una cartella pro sa memòria esterna. Su sistema no est in gradu de frunire su permissu! Permite Iscurre cara a bassu pro sa biblioteca diff --git a/core/src/main/res/values-sk/strings.xml b/core/src/main/res/values-sk/strings.xml index 0bfc56f68b..830c27581c 100644 --- a/core/src/main/res/values-sk/strings.xml +++ b/core/src/main/res/values-sk/strings.xml @@ -289,7 +289,6 @@ Povolené Nie je povolené Pre stiahnutie obsahu zapnite wifi - Prosím vyberte priečinok pre externé úložisko. Systému sa nedarí udeliť povolenie! Povoliť Žiadne poznámky diff --git a/core/src/main/res/values-sl/strings.xml b/core/src/main/res/values-sl/strings.xml index fc52f12335..9b3bd24603 100644 --- a/core/src/main/res/values-sl/strings.xml +++ b/core/src/main/res/values-sl/strings.xml @@ -310,7 +310,6 @@ Dovoljeno Ni dovoljeno Za prenos vsebine omogočite Wi-Fi - Izberite mapo za zunanje shranjevanje. Sistem ne more podeliti dovoljenja! Dovoli Podrsajte navzdol za knjižnico diff --git a/core/src/main/res/values-sq/strings.xml b/core/src/main/res/values-sq/strings.xml index e3c206ec31..16843da249 100644 --- a/core/src/main/res/values-sq/strings.xml +++ b/core/src/main/res/values-sq/strings.xml @@ -293,7 +293,6 @@ Të lejuar Jo të lejuar Që të shkarkohet lënda, ju lutemi, aktivizoni wifi-n - Ju lutemi, përzgjidhni një dosje për depozitim të jashtëm. Sistemi s’është në gjendje të akordojë leje! Lejoje Për Bibliotekën, Fërkojeni Për Poshtë diff --git a/core/src/main/res/values-sv/strings.xml b/core/src/main/res/values-sv/strings.xml index c3d9cee2d5..de939bfe51 100644 --- a/core/src/main/res/values-sv/strings.xml +++ b/core/src/main/res/values-sv/strings.xml @@ -313,7 +313,6 @@ Tillåten Inte tillåtet Aktivera wifi för att ladda ner innehåll - Välj mapp för extern lagring. Systemet kunde inte ge tillstånd! Tillåt Svep nedåt för bibliotek diff --git a/core/src/main/res/values-sw/strings.xml b/core/src/main/res/values-sw/strings.xml index 304337fbbe..692a23f927 100644 --- a/core/src/main/res/values-sw/strings.xml +++ b/core/src/main/res/values-sw/strings.xml @@ -311,7 +311,6 @@ Halali Hairuhusiwi Tafadhali wezesha wifi kupakua maudhui - Tafadhali chagua folda kwa hifadhi ya nje. Mfumo hauwezi kutoa ruhusa! Ruhusu Telezesha kidole Chini kwa Maktaba diff --git a/core/src/main/res/values-ta/strings.xml b/core/src/main/res/values-ta/strings.xml index 3791ab2c1c..3abeb5e60f 100644 --- a/core/src/main/res/values-ta/strings.xml +++ b/core/src/main/res/values-ta/strings.xml @@ -295,7 +295,6 @@ அனுமதிக்கப்படுகிறது அனுமதி இல்லை உள்ளடக்கத்தைப் பதிவிறக்க வைஃபையை இயக்கவும் - வெளிப்புற சேமிப்பகத்திற்கான கோப்புறையைத் தேர்ந்தெடுக்கவும். அமைப்பு அனுமதி வழங்க முடியவில்லை! அனுமதிக்கவும் குறிப்புகள் இல்லை diff --git a/core/src/main/res/values-te/strings.xml b/core/src/main/res/values-te/strings.xml index 5a57ed783b..ac75e6d5c3 100644 --- a/core/src/main/res/values-te/strings.xml +++ b/core/src/main/res/values-te/strings.xml @@ -302,7 +302,6 @@ అనుమతించబడింది ప్రవేశము లేదు దయచేసి కంటెంట్‌ని డౌన్‌లోడ్ చేయడానికి వైఫైని ప్రారంభించండి - దయచేసి బాహ్య నిల్వ కోసం ఫోల్డర్‌ను ఎంచుకోండి. సిస్టమ్ అనుమతి ఇవ్వలేకపోయింది! అనుమతించు లైబ్రరీ కోసం క్రిందికి స్వైప్ చేయండి diff --git a/core/src/main/res/values-tn/strings.xml b/core/src/main/res/values-tn/strings.xml index 2d48f73870..0b4ba57f35 100644 --- a/core/src/main/res/values-tn/strings.xml +++ b/core/src/main/res/values-tn/strings.xml @@ -301,7 +301,6 @@ Go letleletswe Ga go a letlelelwa Tsweetswee letlelela wifi go tsaya tshedimosetso - Tsweetswee tlhopha sekgwama sa go boloka kwa ntle. Tsamaiso ga e kgone go fa tetla! Dumelela Kgaphela kwa tlase kwa motlong wa dibuka diff --git a/core/src/main/res/values-tr/strings.xml b/core/src/main/res/values-tr/strings.xml index 09b5b9c6cd..88b1aab3d5 100644 --- a/core/src/main/res/values-tr/strings.xml +++ b/core/src/main/res/values-tr/strings.xml @@ -317,7 +317,6 @@ İzin verildi İzin verilmedi Lütfen içeriği indirmek için wifi özelliğini etkinleştirin - Lütfen harici depolama için bir klasör seçin. Sistem izin veremiyor! İzin ver Kitaplık için Aşağı Kaydırın diff --git a/core/src/main/res/values-yo/strings.xml b/core/src/main/res/values-yo/strings.xml index b96d22a8dc..3e96182ea3 100644 --- a/core/src/main/res/values-yo/strings.xml +++ b/core/src/main/res/values-yo/strings.xml @@ -305,7 +305,6 @@ Ti gba laaye Ko si aaye Jọwọ jẹ ki wifi gba akoonu lati ayelujara - Jọwọ yan folda kan fun ibi ipamọ ita. Eto ko le funni ni igbanilaaye! Gba laaye Ra Isalẹ fun Library diff --git a/core/src/main/res/values-zh-rTW/strings.xml b/core/src/main/res/values-zh-rTW/strings.xml index eb2b70a41d..05bc186348 100644 --- a/core/src/main/res/values-zh-rTW/strings.xml +++ b/core/src/main/res/values-zh-rTW/strings.xml @@ -331,7 +331,6 @@ 允許 不允許 請啟用 wifi 來下載內容 - 請選取用於外部儲存的資料夾。 系統無法授予權限! 允許 向下滑動圖書館 diff --git a/core/src/main/res/values-zh/strings.xml b/core/src/main/res/values-zh/strings.xml index 5ce4eb0058..aa40cbcfc0 100644 --- a/core/src/main/res/values-zh/strings.xml +++ b/core/src/main/res/values-zh/strings.xml @@ -348,7 +348,6 @@ 允许 不允许 请启用 WiFi 来下载内容 - 请选择用于外部存储的文件夹。 系统无法授予权限! 允许 下滑查看图书馆 diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index b6a3d68370..cc7703127d 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -334,7 +334,6 @@ Not allowed Please enable wifi to download content /Android - Please select a folder for external storage. System unable to grant permission! Allow Swipe Down for Library From a41899bae6b91213229bbfd05ddcf7d652dc7a8b Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Tue, 6 Aug 2024 18:44:31 +0530 Subject: [PATCH 34/41] Fixed the issue where the Pause/Resume button was enabled when a download failed. In such cases, the pause/resume functionality does not work, so we have disabled the button to prevent unexpected behavior. --- .../libraryView/adapter/LibraryViewHolder.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 30c740bd06..73df7c1e42 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -127,16 +127,18 @@ sealed class LibraryViewHolder(containerView: View) : itemDownloadBinding.pauseResume.apply { setImageDrawableCompat(pauseResumeIconId) if (it == itemDownloadBinding.root.context.getString(R.string.paused_state) || - !it.contains(itemDownloadBinding.root.context.getString(R.string.paused_state)) + !it.contains(itemDownloadBinding.root.context.getString(R.string.paused_state)) || + !it.contains(itemDownloadBinding.root.context.getString(R.string.failed_state)) ) { // If the download is paused by the user or is currently running, // enable the pause/resume button. isEnabled = true alpha = 1f } else { - // Otherwise, disable the pause/resume button because the download could be paused - // due to waiting for a WiFi connection if the user tries to download - // the ZIM files over WiFi only. + // Otherwise, disable the pause/resume button because the download could not be paused + // due to waiting for a WiFi connection or another condition that prevents the + // download from continuing, such as a failed state. Disabling the button + // prevents unexpected behavior when the download cannot be resumed. isEnabled = false alpha = 0.5f } From 64c80ac6ddd1b6bb2cfd98b0282271155b8d1937 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Wed, 7 Aug 2024 18:41:08 +0530 Subject: [PATCH 35/41] Fixing the pause/resume functionality --- .../nav/destination/library/OnlineLibraryFragment.kt | 12 +++++++++++- .../libraryView/adapter/LibraryViewHolder.kt | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index a190743640..f826817170 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -134,7 +134,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { Log.e("STATUS", ": ${it.downloadState.toReadableState(context)}") downloader.pauseResumeDownload( it.downloadId, - it.downloadState.toReadableState(context) == getString(R.string.paused_state) + it.downloadState.toReadableState(context).contains(getString(R.string.paused_state)) ) } } @@ -143,6 +143,16 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { ) } + private fun isDownloadPause(downloadState: String): Boolean { + return if (downloadState.contains(getString(R.string.paused_state))) { + true + } else if (downloadState != getString(R.string.pending_state)) { + false + } else { + false + } + } + private val noWifiWithWifiOnlyPreferenceSet get() = sharedPreferenceUtil.prefWifiOnly && !NetworkUtils.isWiFi(requireContext()) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 73df7c1e42..074eb3adf8 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -128,7 +128,8 @@ sealed class LibraryViewHolder(containerView: View) : setImageDrawableCompat(pauseResumeIconId) if (it == itemDownloadBinding.root.context.getString(R.string.paused_state) || !it.contains(itemDownloadBinding.root.context.getString(R.string.paused_state)) || - !it.contains(itemDownloadBinding.root.context.getString(R.string.failed_state)) + !it.contains(itemDownloadBinding.root.context.getString(R.string.failed_state)) || + !it.contains(itemDownloadBinding.root.context.getString(R.string.pending_state)) ) { // If the download is paused by the user or is currently running, // enable the pause/resume button. From df9bbb0ad3b29f4f6a34b960546973d096d05d60 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 8 Aug 2024 14:52:22 +0530 Subject: [PATCH 36/41] Fixed the pause/resume functionality: * When there is no available connection that the user started the download with, the pause/resume button is disabled to prevent attempts to resume or pause the download, which would not work in this state. * If the user attempts to resume the download but it does not resume due to some issue, the code will automatically try to resume the download. This ensures a smooth user experience. * Proper pause reasons are displayed to the user, keeping them informed about the current state of the download. * If a download fails for any reason, the pause/resume button is disabled. At this point, the pause/resume functionality is unnecessary and would not work, so the button is disabled to avoid confusion. * To ensure clarity and understanding, we have added necessary comments on the method and conditions. --- .../library/OnlineLibraryFragment.kt | 12 ---- .../libraryView/adapter/LibraryViewHolder.kt | 34 ++++++--- .../downloadManager/DownloadManagerMonitor.kt | 69 ++++++++++++++++--- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index f826817170..203904d83e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -28,7 +28,6 @@ import android.net.ConnectivityManager import android.os.Build import android.os.Bundle import android.provider.Settings -import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -131,7 +130,6 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { }, { context?.let { context -> - Log.e("STATUS", ": ${it.downloadState.toReadableState(context)}") downloader.pauseResumeDownload( it.downloadId, it.downloadState.toReadableState(context).contains(getString(R.string.paused_state)) @@ -143,16 +141,6 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { ) } - private fun isDownloadPause(downloadState: String): Boolean { - return if (downloadState.contains(getString(R.string.paused_state))) { - true - } else if (downloadState != getString(R.string.pending_state)) { - false - } else { - false - } - } - private val noWifiWithWifiOnlyPreferenceSet get() = sharedPreferenceUtil.prefWifiOnly && !NetworkUtils.isWiFi(requireContext()) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 074eb3adf8..9dfca112cf 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -21,8 +21,10 @@ package org.kiwix.kiwixmobile.zimManager.libraryView.adapter import android.view.View import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.core.base.adapter.BaseViewHolder +import org.kiwix.kiwixmobile.core.downloader.downloadManager.Error import org.kiwix.kiwixmobile.core.downloader.downloadManager.Status import org.kiwix.kiwixmobile.core.downloader.model.Base64String +import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import org.kiwix.kiwixmobile.core.extensions.setBitmap import org.kiwix.kiwixmobile.core.extensions.setImageDrawableCompat import org.kiwix.kiwixmobile.core.extensions.setTextAndVisibility @@ -126,20 +128,10 @@ sealed class LibraryViewHolder(containerView: View) : } itemDownloadBinding.pauseResume.apply { setImageDrawableCompat(pauseResumeIconId) - if (it == itemDownloadBinding.root.context.getString(R.string.paused_state) || - !it.contains(itemDownloadBinding.root.context.getString(R.string.paused_state)) || - !it.contains(itemDownloadBinding.root.context.getString(R.string.failed_state)) || - !it.contains(itemDownloadBinding.root.context.getString(R.string.pending_state)) - ) { - // If the download is paused by the user or is currently running, - // enable the pause/resume button. + if (shouldEnablePauseResumeButton(item.downloadState)) { isEnabled = true alpha = 1f } else { - // Otherwise, disable the pause/resume button because the download could not be paused - // due to waiting for a WiFi connection or another condition that prevents the - // download from continuing, such as a failed state. Disabling the button - // prevents unexpected behavior when the download cannot be resumed. isEnabled = false alpha = 0.5f } @@ -150,6 +142,26 @@ sealed class LibraryViewHolder(containerView: View) : } itemDownloadBinding.eta.text = item.readableEta } + + private fun shouldEnablePauseResumeButton( + downloadState: DownloadState + ): Boolean = + when (downloadState) { + is DownloadState.Failed -> false + is DownloadState.Paused -> { + when (downloadState.reason) { + // Disable the pause button when the DownloadManager is waiting for + // Wi-Fi or network connection. This prevents the user from trying + // to resume the download, as it will not work without a connection. + Error.QUEUED_FOR_WIFI, + Error.WAITING_FOR_NETWORK -> false + + else -> true + } + } + + else -> true + } } class LibraryDividerViewHolder(private val libraryDividerBinding: LibraryDividerBinding) : diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 630387e57e..8e2e776dbd 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -370,17 +370,9 @@ class DownloadManagerMonitor @Inject constructor( synchronized(lock) { updater.onNext { downloadRoomDao.getEntityForDownloadId(downloadId)?.let { downloadEntity -> - if (shouldUpdateStatus(downloadEntity)) { + if (shouldUpdateDownloadStatus(downloadEntity)) { val downloadModel = DownloadModel(downloadEntity).apply { - if (status == Status.PAUSED && downloadEntity.status == Status.QUEUED) { - // Check if the user has resumed the download. - // Do not update the download status immediately since the download manager - // takes some time to actually resume the download. During this time, - // it will still return the paused state. - // By not updating the status right away, we ensure that the user - // sees the "Pending" state, indicating that the download is in the process - // of resuming. - } else { + if (shouldUpdateDownloadStatus(status, error, downloadEntity)) { state = status } this.error = error @@ -408,6 +400,61 @@ class DownloadManagerMonitor @Inject constructor( } } + /** + * Determines whether the download status should be updated based on the current status and error. + * + * This method checks the current download status and error, and decides whether to update the status + * of the download entity. Specifically, it handles the case where a download is paused but has been + * queued for resumption. In such cases, it ensures that the download manager is instructed to resume + * the download, and prevents the status from being prematurely updated to "Paused". + * + * @param status The current status of the download. + * @param error The current error state of the download. + * @param downloadRoomEntity The download entity containing the current status and download ID. + * @return `true` if the status should be updated, `false` otherwise. + */ + private fun shouldUpdateDownloadStatus( + status: Status, + error: Error, + downloadRoomEntity: DownloadRoomEntity + ): Boolean { + synchronized(lock) { + return@shouldUpdateDownloadStatus if ( + status == Status.PAUSED && + downloadRoomEntity.status == Status.QUEUED + ) { + // Check if the user has resumed the download. + // Do not update the download status immediately since the download manager + // takes some time to actually resume the download. During this time, + // it will still return the paused state. + // By not updating the status right away, we ensure that the user + // sees the "Pending" state, indicating that the download is in the process + // of resuming. + when (error) { + // When the pause reason is unknown or waiting to retry, and the user + // resumes the download, attempt to resume the download if it was not resumed + // due to some reason. + Error.PAUSED_UNKNOWN, + Error.WAITING_TO_RETRY -> { + pauseResumeDownloadInDownloadManagerContentResolver( + downloadRoomEntity.downloadId, + CONTROL_RUN, + STATUS_RUNNING + ) + false + } + + // Return true to update the status of the download if there is any other status, + // e.g., WAITING_FOR_WIFI, WAITING_FOR_NETWORK, or any other pause reason + // to inform the user. + else -> true + } + } else { + true + } + } + } + private fun cancelNotification(downloadId: Long) { downloadNotificationManager.cancelNotification(downloadId.toInt()) } @@ -494,7 +541,7 @@ class DownloadManagerMonitor @Inject constructor( } } - private fun shouldUpdateStatus(downloadRoomEntity: DownloadRoomEntity) = + private fun shouldUpdateDownloadStatus(downloadRoomEntity: DownloadRoomEntity) = downloadRoomEntity.status != Status.COMPLETED override fun init() { From 6b60a8c4448bfa054b6a3400e1fe99e5f5564a61 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Thu, 8 Aug 2024 16:31:33 +0530 Subject: [PATCH 37/41] Removed the unnecessary variables and logs from code --- .../downloadManager/DownloadManagerMonitor.kt | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt index 8e2e776dbd..db975d6ffb 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadManagerMonitor.kt @@ -36,7 +36,6 @@ import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadState -import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.files.Log import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -62,8 +61,7 @@ class DownloadManagerMonitor @Inject constructor( private val downloadManager: DownloadManager, val downloadRoomDao: DownloadRoomDao, private val context: Context, - private val downloadNotificationManager: DownloadNotificationManager, - private val sharedPreferenceUtil: SharedPreferenceUtil + private val downloadNotificationManager: DownloadNotificationManager ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val updater = PublishSubject.create<() -> Unit>() @@ -193,12 +191,7 @@ class DownloadManagerMonitor @Inject constructor( bytesDownloaded, totalBytes, reason - ).also { - Log.e( - "UPDATE_STATUS", - "handlePausedDownload: $status, reason $reason" - ) - } + ) DownloadManager.STATUS_PENDING -> handlePendingDownload(downloadId) DownloadManager.STATUS_RUNNING -> handleRunningDownload( @@ -436,11 +429,7 @@ class DownloadManagerMonitor @Inject constructor( // due to some reason. Error.PAUSED_UNKNOWN, Error.WAITING_TO_RETRY -> { - pauseResumeDownloadInDownloadManagerContentResolver( - downloadRoomEntity.downloadId, - CONTROL_RUN, - STATUS_RUNNING - ) + resumeDownload(downloadRoomEntity.downloadId) false } From 1df1cab9ace38f864a11521562cca1b20641ca88 Mon Sep 17 00:00:00 2001 From: CalebK Date: Thu, 8 Aug 2024 13:01:53 +0300 Subject: [PATCH 38/41] minor improvements on shouldEnablePauseResumeButton --- .../libraryView/adapter/LibraryViewHolder.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 9dfca112cf..4881eb36c4 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -148,20 +148,17 @@ sealed class LibraryViewHolder(containerView: View) : ): Boolean = when (downloadState) { is DownloadState.Failed -> false - is DownloadState.Paused -> { - when (downloadState.reason) { - // Disable the pause button when the DownloadManager is waiting for - // Wi-Fi or network connection. This prevents the user from trying - // to resume the download, as it will not work without a connection. - Error.QUEUED_FOR_WIFI, - Error.WAITING_FOR_NETWORK -> false - - else -> true - } - } - + is DownloadState.Paused -> !shouldEnablePauseResumeButton(downloadState.reason) else -> true } + + /** + * Disable the pause button when the DownloadManager is waiting for + * Wi-Fi or network connection. This prevents the user from trying + * to resume the download, as it will not work without a connection. + */ + private fun shouldEnablePauseResumeButton(reason: Error?): Boolean = + reason in listOf(Error.QUEUED_FOR_WIFI, Error.WAITING_FOR_NETWORK) } class LibraryDividerViewHolder(private val libraryDividerBinding: LibraryDividerBinding) : From a267ae835d31c2fe96a6940de06770d3fa431d39 Mon Sep 17 00:00:00 2001 From: CalebK Date: Fri, 9 Aug 2024 09:32:34 +0300 Subject: [PATCH 39/41] refactor shouldEnablePauseResumeButtonForPauseReason to clarify functionality of the function --- .../zimManager/libraryView/adapter/LibraryViewHolder.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt index 4881eb36c4..e6be353908 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/libraryView/adapter/LibraryViewHolder.kt @@ -148,7 +148,7 @@ sealed class LibraryViewHolder(containerView: View) : ): Boolean = when (downloadState) { is DownloadState.Failed -> false - is DownloadState.Paused -> !shouldEnablePauseResumeButton(downloadState.reason) + is DownloadState.Paused -> shouldEnablePauseResumeButtonForPauseReason(downloadState.reason) else -> true } @@ -157,8 +157,8 @@ sealed class LibraryViewHolder(containerView: View) : * Wi-Fi or network connection. This prevents the user from trying * to resume the download, as it will not work without a connection. */ - private fun shouldEnablePauseResumeButton(reason: Error?): Boolean = - reason in listOf(Error.QUEUED_FOR_WIFI, Error.WAITING_FOR_NETWORK) + private fun shouldEnablePauseResumeButtonForPauseReason(reason: Error?): Boolean = + reason !in listOf(Error.QUEUED_FOR_WIFI, Error.WAITING_FOR_NETWORK) } class LibraryDividerViewHolder(private val libraryDividerBinding: LibraryDividerBinding) : From c9b43117e6c3b011f8faa655253bc0759dea74a5 Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 12 Aug 2024 14:44:36 +0530 Subject: [PATCH 40/41] Removed the fetch library from README file. * Also, now we are not using fetch library anymore so we have removed this from our credits files. * Removed the unused code from detekt_baseline.xml file. --- README.md | 2 -- app/src/main/assets/credits.html | 5 ----- core/detekt_baseline.xml | 3 --- .../kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt | 5 ----- custom/src/main/assets/credits.html | 5 ----- 5 files changed, 20 deletions(-) diff --git a/README.md b/README.md index bf4ddd5901..df06b01d45 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,6 @@ are interested in our custom apps, they have their own repo classes by default. - [JUnit5](https://github.com/junit-team/junit5/) - The next generation of JUnit - [AssertJ](https://github.com/joel-costigliola/assertj-core) - Fluent assertions for test code -- [Fetch](https://github.com/tonyofrancis/Fetch) - A customizable file download manager library for - Android - [ZXing](https://github.com/zxing/zxing) - Barcode scanning library for Java, Android ## Contributing diff --git a/app/src/main/assets/credits.html b/app/src/main/assets/credits.html index cd0a10b109..255ee8fa59 100644 --- a/app/src/main/assets/credits.html +++ b/app/src/main/assets/credits.html @@ -77,11 +77,6 @@

Apache


Copyright 2013 Evan Tatarka -
  • - Fetch -
    - Copyright (C) 2017 Tonyo Francis -
  • ObjectBox
    diff --git a/core/detekt_baseline.xml b/core/detekt_baseline.xml index 1ed5c04a82..86d05178c0 100644 --- a/core/detekt_baseline.xml +++ b/core/detekt_baseline.xml @@ -3,7 +3,6 @@ EmptyFunctionBlock:BooksOnDiskViewHolder.kt$BookOnDiskViewHolder.BookViewHolder${ } - EmptyFunctionBlock:FetchDownloadMonitor.kt$FetchDownloadMonitor.<no name provided>${} EmptyFunctionBlock:OnSwipeTouchListener.kt$OnSwipeTouchListener${} ForbiddenComment:JNIInitialiser.kt$JNIInitialiser$// TODO: Consider surfacing to user ForbiddenComment:NetworkUtilsTest.kt$NetworkUtilsTest$// TODO: find a way to assert regex matching via JUnit and rewrite the test @@ -20,8 +19,6 @@ MagicNumber:DownloadItem.kt$DownloadItem$1000L MagicNumber:SearchState.kt$SearchState$100 MagicNumber:DownloaderModule.kt$DownloaderModule$5 - MagicNumber:FetchDownloadNotificationManager.kt$FetchDownloadNotificationManager$100 - MagicNumber:FetchDownloadRequester.kt$10 MagicNumber:FileUtils.kt$FileUtils$3 MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024 MagicNumber:KiloByte.kt$KiloByte$1024.0 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt index 42d1f76797..a1d2609ca2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt @@ -88,11 +88,6 @@ class ApplicationModule { @Provides fun provideComputationThread(): Scheduler = Schedulers.computation() - // @Provides - // @Singleton - // internal fun provideDownloadMonitor(fetchDownloadMonitor: FetchDownloadMonitor): DownloadMonitor = - // fetchDownloadMonitor - @Provides @Singleton internal fun provideDownloadMonitor( diff --git a/custom/src/main/assets/credits.html b/custom/src/main/assets/credits.html index f5c3c70de7..38068149bb 100644 --- a/custom/src/main/assets/credits.html +++ b/custom/src/main/assets/credits.html @@ -78,11 +78,6 @@

    Apache


    Copyright 2013 Evan Tatarka
  • -
  • - Fetch -
    - Copyright (C) 2017 Tonyo Francis -
  • ObjectBox
    From d34dd376d7bea204fb610fbea23f0fb547a8e26f Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Mon, 12 Aug 2024 17:47:03 +0530 Subject: [PATCH 41/41] Improved the migration of storage for existing users according to DownloadManager's storage configuration. --- .../kiwix/kiwixmobile/main/KiwixMainActivity.kt | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt index ed2432bd1a..0f726801fe 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt @@ -40,8 +40,6 @@ import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE import org.kiwix.kiwixmobile.core.main.CoreMainActivity -import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION -import org.kiwix.kiwixmobile.core.utils.INTERNAL_SELECT_POSITION import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange import org.kiwix.kiwixmobile.databinding.ActivityKiwixMainBinding import org.kiwix.kiwixmobile.kiwixActivityComponent @@ -122,16 +120,11 @@ class KiwixMainActivity : CoreMainActivity() { private fun migrateInternalToPublicAppDirectory() { if (!sharedPreferenceUtil.prefIsAppDirectoryMigrated) { - val writableStoragePaths = StorageDeviceUtils.getWritableStorage(this) - val targetStoragePath = when (sharedPreferenceUtil.storagePosition) { - INTERNAL_SELECT_POSITION -> - sharedPreferenceUtil.getPublicDirectoryPath(writableStoragePaths[0].name) - - EXTERNAL_SELECT_POSITION -> writableStoragePaths.getOrNull(1)?.name - else -> null - } - targetStoragePath?.let { - sharedPreferenceUtil.putPrefStorage(it) + val storagePath = StorageDeviceUtils.getWritableStorage(this) + .getOrNull(sharedPreferenceUtil.storagePosition) + ?.name + storagePath?.let { + sharedPreferenceUtil.putPrefStorage(sharedPreferenceUtil.getPublicDirectoryPath(it)) sharedPreferenceUtil.putPrefAppDirectoryMigrated(true) } }