diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt index 9407586dd..2dcb640bd 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt @@ -83,7 +83,7 @@ class KiwixReaderFragment : CoreReaderFragment() { ) } activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) - toolbar?.let(activity::setupDrawerToggle) + toolbar?.let { activity.setupDrawerToggle(it, true) } setFragmentContainerBottomMarginToSizeOfNavBar() openPageInBookFromNavigationArguments() } @@ -167,7 +167,7 @@ class KiwixReaderFragment : CoreReaderFragment() { override fun hideTabSwitcher() { actionBar?.let { actionBar -> actionBar.setDisplayShowTitleEnabled(true) - toolbar?.let { activity?.setupDrawerToggle(it) } + toolbar?.let { activity?.setupDrawerToggle(it, true) } setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) 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 8b0cae71e..c31a939fd 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt @@ -205,11 +205,15 @@ class ZimManageViewModel @Inject constructor( .callTimeout(CALL_TIMEOUT, SECONDS) .addNetworkInterceptor(UserAgentInterceptor(USER_AGENT)) .build() - client.newCall(headRequest).execute().use { response -> - if (response.isSuccessful) { - return@getContentLengthOfLibraryXmlFile response.header("content-length")?.toLongOrNull() - ?: DEFAULT_INT_VALUE.toLong() + try { + client.newCall(headRequest).execute().use { response -> + if (response.isSuccessful) { + return@getContentLengthOfLibraryXmlFile response.header("content-length")?.toLongOrNull() + ?: DEFAULT_INT_VALUE.toLong() + } } + } catch (ignore: Exception) { + // do nothing } return DEFAULT_INT_VALUE.toLong() } 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 2704d5fbc..4d4120215 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,57 +18,85 @@ package org.kiwix.kiwixmobile.core.downloader.downloadManager -import android.annotation.SuppressLint import android.app.DownloadManager import android.content.Context import android.content.Intent -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers 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.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_QUERY_DOWNLOAD_STATUS import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME -import org.kiwix.kiwixmobile.core.extensions.registerReceiver -import org.kiwix.kiwixmobile.core.zim_manager.ConnectivityBroadcastReceiver +import org.kiwix.kiwixmobile.core.extensions.isServiceRunning +import org.kiwix.kiwixmobile.core.utils.files.Log +import java.util.concurrent.TimeUnit import javax.inject.Inject class DownloadManagerMonitor @Inject constructor( val downloadRoomDao: DownloadRoomDao, - private val context: Context, - private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver + private val context: Context ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { private val lock = Any() + private var monitoringDisposable: Disposable? = null init { - context.registerReceiver(connectivityBroadcastReceiver) - startServiceIfActiveDownloads() - trackNetworkState() + startMonitoringDownloads() } - @SuppressLint("CheckResult") - private fun trackNetworkState() { - connectivityBroadcastReceiver.networkStates - .distinctUntilChanged() + @Suppress("MagicNumber") + fun startMonitoringDownloads() { + if (monitoringDisposable?.isDisposed == false) return + monitoringDisposable = Observable.interval(ZERO.toLong(), 5, TimeUnit.SECONDS) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) .subscribe( { - // Start the service when the network changes so that we can - // track the progress accurately. - startServiceIfActiveDownloads() + try { + synchronized(lock) { + // Observe downloads when the application is in the foreground. + // This is especially useful when downloads are resumed but the + // Download Manager takes some time to update the download status. + // In such cases, the foreground service may stop prematurely due to + // a lack of active downloads during this update delay. + if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { + // Check if there are active downloads and the service is not running. + // If so, start the DownloadMonitorService to properly track download progress. + if (shouldStartService()) { + startService() + } else { + // Do nothing; it is for fixing the error when "if" is used as an expression. + } + } else { + monitoringDisposable?.dispose() + } + } + } catch (ignore: Exception) { + Log.e( + "DOWNLOAD_MONITOR", + "Couldn't get the downloads update. Original exception = $ignore" + ) + } }, Throwable::printStackTrace ) } - private fun startServiceIfActiveDownloads() { - CoroutineScope(Dispatchers.IO).launch { - if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { - startService() - } + /** + * Determines if the DownloadMonitorService should be started. + * Checks if there are active downloads and if the service is not already running. + */ + private fun shouldStartService(): Boolean = + getActiveDownloads().isNotEmpty() && + !context.isServiceRunning(DownloadMonitorService::class.java) + + private fun getActiveDownloads(): List = + downloadRoomDao.downloadRoomEntity().blockingFirst().filter { + it.status != Status.PAUSED && it.status != Status.CANCELLED } - } override fun downloadCompleteOrCancelled(intent: Intent) { synchronized(lock) { @@ -86,24 +114,23 @@ class DownloadManagerMonitor @Inject constructor( } } - fun startMonitoringDownloads() { - startService() - } - private fun startService() { context.startService(Intent(context, DownloadMonitorService::class.java)) } fun pauseDownload(downloadId: Long) { context.startService(getDownloadMonitorIntent(ACTION_PAUSE, downloadId.toInt())) + startMonitoringDownloads() } fun resumeDownload(downloadId: Long) { context.startService(getDownloadMonitorIntent(ACTION_RESUME, downloadId.toInt())) + startMonitoringDownloads() } fun cancelDownload(downloadId: Long) { context.startService(getDownloadMonitorIntent(ACTION_CANCEL, downloadId.toInt())) + startMonitoringDownloads() } private fun getDownloadMonitorIntent(action: String, downloadId: Int): Intent = diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadMonitorService.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadMonitorService.kt index 83211007b..44fc93ea8 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadMonitorService.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/downloader/downloadManager/DownloadMonitorService.kt @@ -188,8 +188,8 @@ class DownloadMonitorService : Service() { 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)) + cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) + val totalBytes = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) val progress = calculateProgress(bytesDownloaded, totalBytes) val etaInMilliSeconds = calculateETA(downloadId, bytesDownloaded, totalBytes) @@ -243,8 +243,8 @@ class DownloadMonitorService : Service() { reason: Int, progress: Int, etaInMilliSeconds: Long, - bytesDownloaded: Int, - totalBytes: Int + bytesDownloaded: Long, + totalBytes: Long ) { val error = mapDownloadError(reason) updateDownloadStatus( @@ -261,8 +261,8 @@ class DownloadMonitorService : Service() { private fun handlePausedDownload( downloadId: Long, progress: Int, - bytesDownloaded: Int, - totalSizeOfDownload: Int, + bytesDownloaded: Long, + totalSizeOfDownload: Long, reason: Int ) { val pauseReason = mapDownloadPauseReason(reason) @@ -288,8 +288,8 @@ class DownloadMonitorService : Service() { downloadId: Long, progress: Int, etaInMilliSeconds: Long, - bytesDownloaded: Int, - totalSizeOfDownload: Int + bytesDownloaded: Long, + totalSizeOfDownload: Long ) { updateDownloadStatus( downloadId, @@ -317,14 +317,14 @@ class DownloadMonitorService : Service() { downloadInfoMap.remove(downloadId) } - private fun calculateProgress(bytesDownloaded: Int, totalBytes: Int): Int = + private fun calculateProgress(bytesDownloaded: Long, totalBytes: Long): Int = if (totalBytes > ZERO) { (bytesDownloaded / totalBytes.toDouble()).times(HUNDERED).toInt() } else { ZERO } - private fun calculateETA(downloadedFileId: Long, bytesDownloaded: Int, totalBytes: Int): Long { + private fun calculateETA(downloadedFileId: Long, bytesDownloaded: Long, totalBytes: Long): Long { val currentTime = System.currentTimeMillis() val downloadInfo = downloadInfoMap.getOrPut(downloadedFileId) { DownloadInfo(startTime = currentTime, initialBytesDownloaded = bytesDownloaded) @@ -376,8 +376,8 @@ class DownloadMonitorService : Service() { error: Error, progress: Int = DEFAULT_INT_VALUE, etaInMilliSeconds: Long = DEFAULT_INT_VALUE.toLong(), - bytesDownloaded: Int = DEFAULT_INT_VALUE, - totalSizeOfDownload: Int = DEFAULT_INT_VALUE + bytesDownloaded: Long = DEFAULT_INT_VALUE.toLong(), + totalSizeOfDownload: Long = DEFAULT_INT_VALUE.toLong() ) { synchronized(lock) { updater.onNext { @@ -392,11 +392,11 @@ class DownloadMonitorService : Service() { this.progress = progress } this.etaInMilliSeconds = etaInMilliSeconds - if (bytesDownloaded != DEFAULT_INT_VALUE) { - this.bytesDownloaded = bytesDownloaded.toLong() + if (bytesDownloaded != DEFAULT_INT_VALUE.toLong()) { + this.bytesDownloaded = bytesDownloaded } - if (totalSizeOfDownload != DEFAULT_INT_VALUE) { - this.totalSizeOfDownload = totalSizeOfDownload.toLong() + if (totalSizeOfDownload != DEFAULT_INT_VALUE.toLong()) { + this.totalSizeOfDownload = totalSizeOfDownload } } downloadRoomDao.update(downloadModel) @@ -614,7 +614,7 @@ class DownloadMonitorService : Service() { data class DownloadInfo( var startTime: Long, - var initialBytesDownloaded: Int + var initialBytesDownloaded: Long ) interface AssignNewForegroundServiceNotification { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt index 21b4bc64d..7d4f2515c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt @@ -100,8 +100,8 @@ object ActivityExtensions { val Activity.cachedComponent: CoreActivityComponent get() = coreMainActivity.cachedComponent - fun Activity.setupDrawerToggle(toolbar: Toolbar) = - coreMainActivity.setupDrawerToggle(toolbar) + fun Activity.setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean = false) = + coreMainActivity.setupDrawerToggle(toolbar, shouldEnableRightDrawer) fun Activity.navigate(fragmentId: Int) { coreMainActivity.navigate(fragmentId) 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 c08faa337..5da7926aa 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 @@ -18,6 +18,8 @@ package org.kiwix.kiwixmobile.core.extensions +import android.app.ActivityManager +import android.app.Service import android.content.Context import android.content.Context.RECEIVER_NOT_EXPORTED import android.content.Intent @@ -113,3 +115,11 @@ fun Context.getBitmapFromDrawable(drawable: Drawable): Bitmap { return bitmap } + +@Suppress("Deprecation") +fun Context.isServiceRunning(serviceClass: Class): Boolean { + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val services = activityManager.getRunningServices(Int.MAX_VALUE) + + return services.any { it.service.className == serviceClass.name && it.foreground } +} 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 9b65ff19e..ed4affa78 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 @@ -220,7 +220,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { override fun onSupportNavigateUp(): Boolean = navController.navigateUp() || super.onSupportNavigateUp() - open fun setupDrawerToggle(toolbar: Toolbar) { + open fun setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean = false) { // Set the initial contentDescription to the hamburger icon. // This method is called from various locations after modifying the navigationIcon. // For example, we previously changed this icon/contentDescription to the "+" button @@ -241,8 +241,10 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { it.syncState() } drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) - // Enable the right drawer - drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) + if (shouldEnableRightDrawer) { + // Enable the right drawer + drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) + } } open fun disableDrawer() { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index 817923592..f71f909bb 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -893,7 +893,9 @@ abstract class CoreReaderFragment : * to verify proper functionality. */ open fun setUpDrawerToggle(toolbar: Toolbar) { - toolbar.let((requireActivity() as CoreMainActivity)::setupDrawerToggle) + toolbar.let { + (requireActivity() as CoreMainActivity).setupDrawerToggle(it, true) + } } /** diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt index cf0797d59..bc3a2f1b5 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt @@ -98,8 +98,8 @@ class CustomMainActivity : CoreMainActivity() { } } - override fun setupDrawerToggle(toolbar: Toolbar) { - super.setupDrawerToggle(toolbar) + override fun setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean) { + super.setupDrawerToggle(toolbar, shouldEnableRightDrawer) activityCustomMainBinding.drawerNavView.apply { /** * Hide the 'ZimHostFragment' option from the navigation menu