Loading app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +1 −0 Original line number Diff line number Diff line Loading @@ -478,6 +478,7 @@ class MainActivityViewModel @Inject constructor( if (shouldShowPaidAppsSnackBar(app)) { return } viewModelScope.launch { val fusedDownload: FusedDownload try { Loading app/src/main/java/foundation/e/apps/api/DownloadManager.kt +26 −1 Original line number Diff line number Diff line Loading @@ -127,11 +127,36 @@ class DownloadManager @Inject constructor( } } private fun tickerFlow(downloadId: Long, period: Duration, initialDelay: Duration = Duration.ZERO) = flow { private fun tickerFlow( downloadId: Long, period: Duration, initialDelay: Duration = Duration.ZERO ) = flow { delay(initialDelay) while (downloadsMaps[downloadId]!!) { emit(Unit) delay(period) } } fun isDownloadSuccessful(downloadId: Long): Boolean { return getDownloadStatus(downloadId) == DownloadManager.STATUS_SUCCESSFUL } private fun getDownloadStatus(downloadId: Long): Int { try { downloadManager.query(downloadManagerQuery.setFilterById(downloadId)) .use { cursor -> if (cursor.moveToFirst()) { val status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) Timber.d("Download Failed: downloadId: $downloadId $status") return status } } } catch (e: Exception) { Timber.e(e) } return DownloadManager.STATUS_FAILED } } app/src/main/java/foundation/e/apps/api/cleanapk/ApkSignatureManager.kt +11 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import org.bouncycastle.openpgp.PGPUtil import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator import timber.log.Timber import java.io.BufferedInputStream import java.io.FileInputStream import java.io.InputStream Loading @@ -35,11 +36,16 @@ import java.security.Security object ApkSignatureManager { fun verifyFdroidSignature(context: Context, apkFilePath: String, signature: String): Boolean { Security.addProvider(BouncyCastleProvider()) try { return verifyAPKSignature( BufferedInputStream(FileInputStream(apkFilePath)), signature.byteInputStream(Charsets.UTF_8), context.assets.open("f-droid.org-signing-key.gpg") ) } catch (e: Exception) { Timber.e(e) } return false } private fun verifyAPKSignature( Loading app/src/main/java/foundation/e/apps/api/fused/UpdatesDao.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 MURENA SAS * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.apps.api.fused import foundation.e.apps.api.fused.data.FusedApp object UpdatesDao { var appsAwaitingForUpdate: List<FusedApp> = listOf() fun hasAnyAppsForUpdate() = appsAwaitingForUpdate.isNotEmpty() } app/src/main/java/foundation/e/apps/manager/download/DownloadManagerUtils.kt +115 −88 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ package foundation.e.apps.manager.download import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.api.DownloadManager import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Origin Loading @@ -36,8 +37,9 @@ import javax.inject.Singleton @Singleton class DownloadManagerUtils @Inject constructor( @ApplicationContext private val context: Context, private val fusedManagerRepository: FusedManagerRepository, @ApplicationContext private val context: Context private val downloadManager: DownloadManager ) { private val TAG = DownloadManagerUtils::class.java.simpleName private val mutex = Mutex() Loading @@ -57,11 +59,13 @@ class DownloadManagerUtils @Inject constructor( delay(1500) // Waiting for downloadmanager to publish the progress of last bytes val fusedDownload = fusedManagerRepository.getFusedDownload(downloadId) if (fusedDownload.id.isNotEmpty()) { fusedDownload.downloadIdMap[downloadId] = true fusedManagerRepository.updateFusedDownload(fusedDownload) val downloaded = fusedDownload.downloadIdMap.values.filter { it }.size Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $downloaded/${fusedDownload.downloadIdMap.size}") if (downloaded == fusedDownload.downloadIdMap.size && checkCleanApkSignatureOK(fusedDownload)) { updateDownloadIdMap(fusedDownload, downloadId) val numberOfDownloadedItems = fusedDownload.downloadIdMap.values.filter { it }.size Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $numberOfDownloadedItems/${fusedDownload.downloadIdMap.size}") if (validateDownload(numberOfDownloadedItems, fusedDownload, downloadId)) { Timber.d("===> Download is completed for: ${fusedDownload.name}") fusedManagerRepository.moveOBBFileToOBBDirectory(fusedDownload) fusedDownload.status = Status.DOWNLOADED fusedManagerRepository.updateFusedDownload(fusedDownload) Loading @@ -71,6 +75,29 @@ class DownloadManagerUtils @Inject constructor( } } private suspend fun validateDownload( numberOfDownloadedItems: Int, fusedDownload: FusedDownload, downloadId: Long ) = downloadManager.isDownloadSuccessful(downloadId) && areAllFilesDownloaded( numberOfDownloadedItems, fusedDownload ) && checkCleanApkSignatureOK(fusedDownload) private fun areAllFilesDownloaded( numberOfDownloadedItems: Int, fusedDownload: FusedDownload ) = numberOfDownloadedItems == fusedDownload.downloadIdMap.size private suspend fun updateDownloadIdMap( fusedDownload: FusedDownload, downloadId: Long ) { fusedDownload.downloadIdMap[downloadId] = true fusedManagerRepository.updateFusedDownload(fusedDownload) } private suspend fun checkCleanApkSignatureOK(fusedDownload: FusedDownload): Boolean { if (fusedDownload.origin != Origin.CLEANAPK || fusedManagerRepository.isFdroidApplicationSigned( context, Loading Loading
app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +1 −0 Original line number Diff line number Diff line Loading @@ -478,6 +478,7 @@ class MainActivityViewModel @Inject constructor( if (shouldShowPaidAppsSnackBar(app)) { return } viewModelScope.launch { val fusedDownload: FusedDownload try { Loading
app/src/main/java/foundation/e/apps/api/DownloadManager.kt +26 −1 Original line number Diff line number Diff line Loading @@ -127,11 +127,36 @@ class DownloadManager @Inject constructor( } } private fun tickerFlow(downloadId: Long, period: Duration, initialDelay: Duration = Duration.ZERO) = flow { private fun tickerFlow( downloadId: Long, period: Duration, initialDelay: Duration = Duration.ZERO ) = flow { delay(initialDelay) while (downloadsMaps[downloadId]!!) { emit(Unit) delay(period) } } fun isDownloadSuccessful(downloadId: Long): Boolean { return getDownloadStatus(downloadId) == DownloadManager.STATUS_SUCCESSFUL } private fun getDownloadStatus(downloadId: Long): Int { try { downloadManager.query(downloadManagerQuery.setFilterById(downloadId)) .use { cursor -> if (cursor.moveToFirst()) { val status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) Timber.d("Download Failed: downloadId: $downloadId $status") return status } } } catch (e: Exception) { Timber.e(e) } return DownloadManager.STATUS_FAILED } }
app/src/main/java/foundation/e/apps/api/cleanapk/ApkSignatureManager.kt +11 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import org.bouncycastle.openpgp.PGPUtil import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator import timber.log.Timber import java.io.BufferedInputStream import java.io.FileInputStream import java.io.InputStream Loading @@ -35,11 +36,16 @@ import java.security.Security object ApkSignatureManager { fun verifyFdroidSignature(context: Context, apkFilePath: String, signature: String): Boolean { Security.addProvider(BouncyCastleProvider()) try { return verifyAPKSignature( BufferedInputStream(FileInputStream(apkFilePath)), signature.byteInputStream(Charsets.UTF_8), context.assets.open("f-droid.org-signing-key.gpg") ) } catch (e: Exception) { Timber.e(e) } return false } private fun verifyAPKSignature( Loading
app/src/main/java/foundation/e/apps/api/fused/UpdatesDao.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 MURENA SAS * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.apps.api.fused import foundation.e.apps.api.fused.data.FusedApp object UpdatesDao { var appsAwaitingForUpdate: List<FusedApp> = listOf() fun hasAnyAppsForUpdate() = appsAwaitingForUpdate.isNotEmpty() }
app/src/main/java/foundation/e/apps/manager/download/DownloadManagerUtils.kt +115 −88 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ package foundation.e.apps.manager.download import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.api.DownloadManager import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Origin Loading @@ -36,8 +37,9 @@ import javax.inject.Singleton @Singleton class DownloadManagerUtils @Inject constructor( @ApplicationContext private val context: Context, private val fusedManagerRepository: FusedManagerRepository, @ApplicationContext private val context: Context private val downloadManager: DownloadManager ) { private val TAG = DownloadManagerUtils::class.java.simpleName private val mutex = Mutex() Loading @@ -57,11 +59,13 @@ class DownloadManagerUtils @Inject constructor( delay(1500) // Waiting for downloadmanager to publish the progress of last bytes val fusedDownload = fusedManagerRepository.getFusedDownload(downloadId) if (fusedDownload.id.isNotEmpty()) { fusedDownload.downloadIdMap[downloadId] = true fusedManagerRepository.updateFusedDownload(fusedDownload) val downloaded = fusedDownload.downloadIdMap.values.filter { it }.size Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $downloaded/${fusedDownload.downloadIdMap.size}") if (downloaded == fusedDownload.downloadIdMap.size && checkCleanApkSignatureOK(fusedDownload)) { updateDownloadIdMap(fusedDownload, downloadId) val numberOfDownloadedItems = fusedDownload.downloadIdMap.values.filter { it }.size Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $numberOfDownloadedItems/${fusedDownload.downloadIdMap.size}") if (validateDownload(numberOfDownloadedItems, fusedDownload, downloadId)) { Timber.d("===> Download is completed for: ${fusedDownload.name}") fusedManagerRepository.moveOBBFileToOBBDirectory(fusedDownload) fusedDownload.status = Status.DOWNLOADED fusedManagerRepository.updateFusedDownload(fusedDownload) Loading @@ -71,6 +75,29 @@ class DownloadManagerUtils @Inject constructor( } } private suspend fun validateDownload( numberOfDownloadedItems: Int, fusedDownload: FusedDownload, downloadId: Long ) = downloadManager.isDownloadSuccessful(downloadId) && areAllFilesDownloaded( numberOfDownloadedItems, fusedDownload ) && checkCleanApkSignatureOK(fusedDownload) private fun areAllFilesDownloaded( numberOfDownloadedItems: Int, fusedDownload: FusedDownload ) = numberOfDownloadedItems == fusedDownload.downloadIdMap.size private suspend fun updateDownloadIdMap( fusedDownload: FusedDownload, downloadId: Long ) { fusedDownload.downloadIdMap[downloadId] = true fusedManagerRepository.updateFusedDownload(fusedDownload) } private suspend fun checkCleanApkSignatureOK(fusedDownload: FusedDownload): Boolean { if (fusedDownload.origin != Origin.CLEANAPK || fusedManagerRepository.isFdroidApplicationSigned( context, Loading