Loading app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "android.arch.lifecycle:extensions:1.1.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" // Coroutines def coroutines_version = "1.6.0" Loading app/src/main/java/foundation/e/apps/api/ResultSupreme.kt +7 −1 Original line number Diff line number Diff line Loading @@ -65,7 +65,7 @@ sealed class ResultSupreme<T> { * No valid data from processing. * Use [isUnknownError] to check. */ class Error<T>() : ResultSupreme<T>() { open class Error<T>() : ResultSupreme<T>() { /** * @param message A String message to log or display to the user. * @param exception Optional exception from try-catch block. Loading @@ -85,6 +85,12 @@ sealed class ResultSupreme<T> { } } class WorkError<T> constructor(data: T, payload: Any? = null) : Error<T>(data) { init { this.otherPayload = payload } } /** * Data from processing. May be null. */ Loading app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt +1 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import foundation.e.apps.api.gplay.utils.GPlayHttpClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpClient) { Loading app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt +37 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.widget.ImageView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController Loading @@ -37,22 +38,30 @@ import foundation.e.apps.AppProgressViewModel import foundation.e.apps.MainActivityViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.FusedAPIInterface import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.applicationlist.ApplicationListRVAdapter import foundation.e.apps.databinding.FragmentUpdatesBinding import foundation.e.apps.login.AuthObject import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.manager.workmanager.InstallWorkManager.INSTALL_WORK_NAME import foundation.e.apps.updates.manager.UpdatesWorkManager import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.exceptions.GPlayException import foundation.e.apps.utils.exceptions.GPlayLoginException import foundation.e.apps.utils.modules.CommonUtilsModule.safeNavigate import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment import foundation.e.apps.utils.toast import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import javax.inject.Inject Loading Loading @@ -163,6 +172,28 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte onTimeout() }*/ } viewLifecycleOwner.lifecycleScope.launch { EventBus.events.flowWithLifecycle(viewLifecycleOwner.lifecycle) .filter { appEvent -> appEvent is AppEvent.UpdateEvent }.collectLatest { val event = it.data as ResultSupreme.WorkError<*> when (event.data) { ResultStatus.USER_NOT_AVAILABLE -> { requireContext().toast(getString(R.string.user_not_available)) } ResultStatus.RETRY -> { requireContext().toast(getString(R.string.message_retry)) } else -> { if (event.otherPayload is FusedDownload) { requireContext().toast("${(event.otherPayload as FusedDownload).name} update is failed!") } else { requireContext().toast(getString(R.string.message_update_failed)) } } } } } } private fun showPurchasedAppMessage(fusedApp: FusedApp) { Loading Loading @@ -272,7 +303,8 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte listOf( WorkInfo.State.FAILED, WorkInfo.State.BLOCKED, WorkInfo.State.CANCELLED WorkInfo.State.CANCELLED, WorkInfo.State.SUCCEEDED ) if (!it.isNullOrEmpty() && errorStates.contains(it.last().state)) { binding.button.isEnabled = true Loading app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorker.kt +51 −14 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ import com.google.gson.Gson import dagger.assisted.Assisted import dagger.assisted.AssistedInject import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.cleanapk.CleanAPKInterface import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp Loading @@ -25,9 +26,13 @@ import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.manager.workmanager.InstallWorkManager import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Type import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.modules.DataStoreModule import kotlinx.coroutines.delay import timber.log.Timber import java.io.ByteArrayOutputStream import java.net.URL Loading @@ -52,6 +57,7 @@ class UpdatesWorker @AssistedInject constructor( private var automaticInstallEnabled = true private var onlyOnUnmeteredNetwork = false private var isAutoUpdate = true // indicates it is auto update or user initiated update private var retryCount = 0 override suspend fun doWork(): Result { return try { Loading @@ -77,30 +83,39 @@ class UpdatesWorker @AssistedInject constructor( val appsNeededToUpdate = mutableListOf<FusedApp>() val user = getUser() val authData = getAuthData() var resultStatus = ResultStatus.OK if (user in listOf(User.ANONYMOUS, User.GOOGLE) && authData != null) { /* * Signifies valid Google user and valid auth data to update * apps from Google Play store. * The user check will be more useful in No Google mode. */ appsNeededToUpdate.addAll(updatesManagerRepository.getUpdates(authData).first) val updateData = updatesManagerRepository.getUpdates(authData) appsNeededToUpdate.addAll(updateData.first) resultStatus = updateData.second } else if (user != User.UNAVAILABLE) { /* * If authData is null, update apps from cleanapk only. */ appsNeededToUpdate.addAll(updatesManagerRepository.getUpdatesOSS().first) val updateData = updatesManagerRepository.getUpdatesOSS() appsNeededToUpdate.addAll(updateData.first) resultStatus = updateData.second } else { /* * If user in UNAVAILABLE, don't do anything. */ EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.USER_NOT_AVAILABLE))) return } if (resultStatus != ResultStatus.OK) { manageRetry() } else { /* * Show notification only if enabled. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5376 */ retryCount = 0 if (isAutoUpdate && shouldShowNotification) { handleNotification(appsNeededToUpdate.size, isConnectedToUnmeteredNetwork) } Loading @@ -116,6 +131,20 @@ class UpdatesWorker @AssistedInject constructor( authData ?: AuthData("", ""), ) } } private suspend fun manageRetry() { retryCount++ if (retryCount == 1) { EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.RETRY))) } if (retryCount <= 10) { delay(3000) checkForUpdates() } else { EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.UNKNOWN))) } } private suspend fun triggerUpdateProcessOnSettings( isConnectedToUnmeteredNetwork: Boolean, Loading Loading @@ -184,7 +213,15 @@ class UpdatesWorker @AssistedInject constructor( try { updateFusedDownloadWithAppDownloadLink(fusedApp, authData, fusedDownload) } catch (e: Exception) { e.printStackTrace() Timber.e(e) EventBus.invokeEvent( AppEvent.UpdateEvent( ResultSupreme.WorkError( ResultStatus.UNKNOWN, fusedDownload ) ) ) return@forEach } Loading Loading
app/build.gradle +1 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "android.arch.lifecycle:extensions:1.1.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" // Coroutines def coroutines_version = "1.6.0" Loading
app/src/main/java/foundation/e/apps/api/ResultSupreme.kt +7 −1 Original line number Diff line number Diff line Loading @@ -65,7 +65,7 @@ sealed class ResultSupreme<T> { * No valid data from processing. * Use [isUnknownError] to check. */ class Error<T>() : ResultSupreme<T>() { open class Error<T>() : ResultSupreme<T>() { /** * @param message A String message to log or display to the user. * @param exception Optional exception from try-catch block. Loading @@ -85,6 +85,12 @@ sealed class ResultSupreme<T> { } } class WorkError<T> constructor(data: T, payload: Any? = null) : Error<T>(data) { init { this.otherPayload = payload } } /** * Data from processing. May be null. */ Loading
app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt +1 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import foundation.e.apps.api.gplay.utils.GPlayHttpClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject class GPlayAPIImpl @Inject constructor(private val gPlayHttpClient: GPlayHttpClient) { Loading
app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt +37 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.widget.ImageView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController Loading @@ -37,22 +38,30 @@ import foundation.e.apps.AppProgressViewModel import foundation.e.apps.MainActivityViewModel import foundation.e.apps.PrivacyInfoViewModel import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.FusedAPIInterface import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.applicationlist.ApplicationListRVAdapter import foundation.e.apps.databinding.FragmentUpdatesBinding import foundation.e.apps.login.AuthObject import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.download.data.DownloadProgress import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.manager.workmanager.InstallWorkManager.INSTALL_WORK_NAME import foundation.e.apps.updates.manager.UpdatesWorkManager import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.exceptions.GPlayException import foundation.e.apps.utils.exceptions.GPlayLoginException import foundation.e.apps.utils.modules.CommonUtilsModule.safeNavigate import foundation.e.apps.utils.modules.PWAManagerModule import foundation.e.apps.utils.parentFragment.TimeoutFragment import foundation.e.apps.utils.toast import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import javax.inject.Inject Loading Loading @@ -163,6 +172,28 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte onTimeout() }*/ } viewLifecycleOwner.lifecycleScope.launch { EventBus.events.flowWithLifecycle(viewLifecycleOwner.lifecycle) .filter { appEvent -> appEvent is AppEvent.UpdateEvent }.collectLatest { val event = it.data as ResultSupreme.WorkError<*> when (event.data) { ResultStatus.USER_NOT_AVAILABLE -> { requireContext().toast(getString(R.string.user_not_available)) } ResultStatus.RETRY -> { requireContext().toast(getString(R.string.message_retry)) } else -> { if (event.otherPayload is FusedDownload) { requireContext().toast("${(event.otherPayload as FusedDownload).name} update is failed!") } else { requireContext().toast(getString(R.string.message_update_failed)) } } } } } } private fun showPurchasedAppMessage(fusedApp: FusedApp) { Loading Loading @@ -272,7 +303,8 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte listOf( WorkInfo.State.FAILED, WorkInfo.State.BLOCKED, WorkInfo.State.CANCELLED WorkInfo.State.CANCELLED, WorkInfo.State.SUCCEEDED ) if (!it.isNullOrEmpty() && errorStates.contains(it.last().state)) { binding.button.isEnabled = true Loading
app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorker.kt +51 −14 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ import com.google.gson.Gson import dagger.assisted.Assisted import dagger.assisted.AssistedInject import foundation.e.apps.R import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.cleanapk.CleanAPKInterface import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp Loading @@ -25,9 +26,13 @@ import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.manager.workmanager.InstallWorkManager import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Type import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.modules.DataStoreModule import kotlinx.coroutines.delay import timber.log.Timber import java.io.ByteArrayOutputStream import java.net.URL Loading @@ -52,6 +57,7 @@ class UpdatesWorker @AssistedInject constructor( private var automaticInstallEnabled = true private var onlyOnUnmeteredNetwork = false private var isAutoUpdate = true // indicates it is auto update or user initiated update private var retryCount = 0 override suspend fun doWork(): Result { return try { Loading @@ -77,30 +83,39 @@ class UpdatesWorker @AssistedInject constructor( val appsNeededToUpdate = mutableListOf<FusedApp>() val user = getUser() val authData = getAuthData() var resultStatus = ResultStatus.OK if (user in listOf(User.ANONYMOUS, User.GOOGLE) && authData != null) { /* * Signifies valid Google user and valid auth data to update * apps from Google Play store. * The user check will be more useful in No Google mode. */ appsNeededToUpdate.addAll(updatesManagerRepository.getUpdates(authData).first) val updateData = updatesManagerRepository.getUpdates(authData) appsNeededToUpdate.addAll(updateData.first) resultStatus = updateData.second } else if (user != User.UNAVAILABLE) { /* * If authData is null, update apps from cleanapk only. */ appsNeededToUpdate.addAll(updatesManagerRepository.getUpdatesOSS().first) val updateData = updatesManagerRepository.getUpdatesOSS() appsNeededToUpdate.addAll(updateData.first) resultStatus = updateData.second } else { /* * If user in UNAVAILABLE, don't do anything. */ EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.USER_NOT_AVAILABLE))) return } if (resultStatus != ResultStatus.OK) { manageRetry() } else { /* * Show notification only if enabled. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5376 */ retryCount = 0 if (isAutoUpdate && shouldShowNotification) { handleNotification(appsNeededToUpdate.size, isConnectedToUnmeteredNetwork) } Loading @@ -116,6 +131,20 @@ class UpdatesWorker @AssistedInject constructor( authData ?: AuthData("", ""), ) } } private suspend fun manageRetry() { retryCount++ if (retryCount == 1) { EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.RETRY))) } if (retryCount <= 10) { delay(3000) checkForUpdates() } else { EventBus.invokeEvent(AppEvent.UpdateEvent(ResultSupreme.WorkError(ResultStatus.UNKNOWN))) } } private suspend fun triggerUpdateProcessOnSettings( isConnectedToUnmeteredNetwork: Boolean, Loading Loading @@ -184,7 +213,15 @@ class UpdatesWorker @AssistedInject constructor( try { updateFusedDownloadWithAppDownloadLink(fusedApp, authData, fusedDownload) } catch (e: Exception) { e.printStackTrace() Timber.e(e) EventBus.invokeEvent( AppEvent.UpdateEvent( ResultSupreme.WorkError( ResultStatus.UNKNOWN, fusedDownload ) ) ) return@forEach } Loading