diff --git a/app/src/gms/java/mega/privacy/android/app/service/push/MegaMessageService.kt b/app/src/gms/java/mega/privacy/android/app/service/push/MegaMessageService.kt index 6ee91616514..5bbc0b2cb92 100644 --- a/app/src/gms/java/mega/privacy/android/app/service/push/MegaMessageService.kt +++ b/app/src/gms/java/mega/privacy/android/app/service/push/MegaMessageService.kt @@ -1,22 +1,33 @@ package mega.privacy.android.app.service.push -import android.content.Context import androidx.work.Data import androidx.work.WorkManager -import com.google.android.gms.tasks.Task import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.tasks.await import mega.privacy.android.app.data.extensions.enqueuePushMessage import mega.privacy.android.app.data.extensions.enqueueUniqueWorkNewToken import mega.privacy.android.app.utils.Constants.DEVICE_ANDROID +import mega.privacy.android.domain.qualifier.ApplicationScope import timber.log.Timber -import java.util.concurrent.Executors +import javax.inject.Inject @AndroidEntryPoint class MegaMessageService : FirebaseMessagingService() { + @Inject + @ApplicationScope + lateinit var applicationScope: CoroutineScope + + @Inject + lateinit var workManager: WorkManager + override fun onDestroy() { Timber.d("onDestroy") super.onDestroy() @@ -27,15 +38,17 @@ class MegaMessageService : FirebaseMessagingService() { val workerData = remoteMessage.data.toWorkerData() - WorkManager.getInstance(this) - .enqueuePushMessage(workerData) + applicationScope.launch { + workManager.enqueuePushMessage(workerData) + } } override fun onNewToken(token: String) { Timber.d("New token: $token") - WorkManager.getInstance(this) - .enqueueUniqueWorkNewToken(token, DEVICE_ANDROID) + applicationScope.launch { + workManager.enqueueUniqueWorkNewToken(token, DEVICE_ANDROID) + } } /** @@ -49,28 +62,24 @@ class MegaMessageService : FirebaseMessagingService() { .build() companion object { + /** + * Mutex to avoid multiple calls to getToken. + */ + private val mutex = Mutex() + /** * Request push service token, then register it in API as an identifier of the device. - * - * @param context Context. */ @JvmStatic - fun getToken(context: Context) { + suspend fun getToken(workManager: WorkManager) { //project number from google-service.json - Executors.newFixedThreadPool(1).submit { - FirebaseMessaging.getInstance().token.addOnCompleteListener { task: Task -> - if (!task.isSuccessful) { - Timber.w("Get token failed.") - return@addOnCompleteListener - } - - // Get new Instance ID token - val token = task.result - Timber.d("Get token: $token") + mutex.withLock { + val token = FirebaseMessaging.getInstance().token.await() - WorkManager.getInstance(context) - .enqueueUniqueWorkNewToken(token, DEVICE_ANDROID) - } + token?.let { + Timber.d("Get token succeeded") + workManager.enqueueUniqueWorkNewToken(token, DEVICE_ANDROID) + } ?: Timber.w("Get token failed.") } } } diff --git a/app/src/main/java/mega/privacy/android/app/data/extensions/WorkManager.kt b/app/src/main/java/mega/privacy/android/app/data/extensions/WorkManager.kt index 42fe64361de..d55005b4e14 100644 --- a/app/src/main/java/mega/privacy/android/app/data/extensions/WorkManager.kt +++ b/app/src/main/java/mega/privacy/android/app/data/extensions/WorkManager.kt @@ -7,13 +7,16 @@ import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager import mega.privacy.android.app.fcm.NewTokenWorker import mega.privacy.android.app.fcm.PushMessageWorker +import mega.privacy.android.data.facade.debugWorkInfo /** * Enqueues a [PushMessageWorker] request to manage a push notification. * * @param data [Data] containing the push information. */ -fun WorkManager.enqueuePushMessage(data: Data) { +suspend fun WorkManager.enqueuePushMessage(data: Data) { + debugWorkInfo() + enqueue( OneTimeWorkRequestBuilder() .setInputData(data) @@ -28,7 +31,9 @@ fun WorkManager.enqueuePushMessage(data: Data) { * @param newToken Required token for register pushes. * @param deviceType Type of device. */ -fun WorkManager.enqueueUniqueWorkNewToken(newToken: String, deviceType: Int) { +suspend fun WorkManager.enqueueUniqueWorkNewToken(newToken: String, deviceType: Int) { + debugWorkInfo() + enqueueUniqueWork( NewTokenWorker.WORK_NAME, ExistingWorkPolicy.REPLACE, @@ -42,4 +47,4 @@ fun WorkManager.enqueueUniqueWorkNewToken(newToken: String, deviceType: Int) { .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() ) -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index fe0f64a68ca..10f54a285d6 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -66,6 +66,7 @@ import androidx.navigation.NavDestination import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment import androidx.viewpager2.widget.ViewPager2 +import androidx.work.WorkManager import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomnavigation.BottomNavigationItemView @@ -83,6 +84,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.kotlin.addTo import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -300,6 +302,7 @@ import mega.privacy.android.domain.exception.QuotaExceededMegaException import mega.privacy.android.domain.exception.chat.IAmOnAnotherCallException import mega.privacy.android.domain.exception.chat.MeetingEndedException import mega.privacy.android.domain.exception.node.ForeignNodeException +import mega.privacy.android.domain.qualifier.ApplicationScope import mega.privacy.android.domain.qualifier.IoDispatcher import mega.privacy.android.domain.usecase.GetChatRoomUseCase import mega.privacy.android.domain.usecase.chat.HasArchivedChatsUseCase @@ -452,6 +455,13 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf @Inject lateinit var syncNavigator: SyncNavigator + @Inject + lateinit var workManager: WorkManager + + @Inject + @ApplicationScope + lateinit var applicationScope: CoroutineScope + //GET PRO ACCOUNT PANEL private lateinit var getProLayout: LinearLayout private lateinit var getProText: TextView @@ -1385,7 +1395,9 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf megaApi.invalidateCache() } dbH.setInvalidateSdkCache(false) - MegaMessageService.getToken(this) + applicationScope.launch { + MegaMessageService.getToken(workManager) + } userInfoViewModel.getUserInfo() preloadPayment() megaApi.isGeolocationEnabled(this) diff --git a/data/src/main/java/mega/privacy/android/data/facade/WorkManagerFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/WorkManagerFacade.kt index aa03a55d45f..75023e03b36 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/WorkManagerFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/WorkManagerFacade.kt @@ -7,8 +7,10 @@ import androidx.work.OneTimeWorkRequest import androidx.work.PeriodicWorkRequest import androidx.work.WorkInfo import androidx.work.WorkManager +import androidx.work.WorkQuery import androidx.work.await import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge import mega.privacy.android.data.gateway.WorkManagerGateway @@ -41,6 +43,8 @@ internal class WorkManagerFacade @Inject constructor( ) : WorkManagerGateway { override suspend fun enqueueDeleteOldestCompletedTransfersWorkRequest() { + workManager.debugWorkInfo() + val workRequest = OneTimeWorkRequest.Builder(DeleteOldestCompletedTransfersWorker::class.java) .addTag(DeleteOldestCompletedTransfersWorker.DELETE_OLDEST_TRANSFERS_WORKER_TAG) @@ -54,7 +58,9 @@ internal class WorkManagerFacade @Inject constructor( ) } - override fun enqueueDownloadsWorkerRequest() { + override suspend fun enqueueDownloadsWorkerRequest() { + workManager.debugWorkInfo() + val request = OneTimeWorkRequest.Builder(DownloadsWorker::class.java) .addTag(DownloadsWorker.SINGLE_DOWNLOAD_TAG) .build() @@ -69,6 +75,8 @@ internal class WorkManagerFacade @Inject constructor( override suspend fun startCameraUploads() { // Check if CU periodic worker is working. If yes, then don't start a single one if (!checkWorkerRunning(CAMERA_UPLOAD_TAG)) { + workManager.debugWorkInfo() + Timber.d("No CU periodic process currently running, proceed with one time request") val cameraUploadWorkRequest = OneTimeWorkRequest.Builder( CameraUploadsWorker::class.java @@ -111,6 +119,9 @@ internal class WorkManagerFacade @Inject constructor( override suspend fun scheduleCameraUploads() { scheduleCameraUploadSyncActiveHeartbeat() + + workManager.debugWorkInfo() + // periodic work that runs during the last 10 minutes of every one hour period val cameraUploadWorkRequest = PeriodicWorkRequest.Builder( CameraUploadsWorker::class.java, @@ -139,6 +150,8 @@ internal class WorkManagerFacade @Inject constructor( * Schedule camera uploads active heartbeat worker */ private suspend fun scheduleCameraUploadSyncActiveHeartbeat() { + workManager.debugWorkInfo() + // periodic work that runs during the last 10 minutes of every half an hour period val cuSyncActiveHeartbeatWorkRequest = PeriodicWorkRequest.Builder( SyncHeartbeatCameraUploadWorker::class.java, @@ -223,3 +236,17 @@ internal class WorkManagerFacade @Inject constructor( override fun monitorDownloadsStatusInfo() = workManager.getWorkInfosByTagFlow(DownloadsWorker.SINGLE_DOWNLOAD_TAG) } + +/** + * Prints the list of pending [WorkInfo] in the log. + */ +suspend fun WorkManager.debugWorkInfo() { + getWorkInfosFlow( + WorkQuery.fromStates( + WorkInfo.State.ENQUEUED, + ) + ).firstOrNull() + ?.map { it.tags } + ?.let { Timber.d("Worker pending list: $it") } + ?: Timber.d("Worker pending list: empty") +} diff --git a/data/src/main/java/mega/privacy/android/data/gateway/WorkManagerGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/WorkManagerGateway.kt index 3099028220e..7a54f513d67 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/WorkManagerGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/WorkManagerGateway.kt @@ -16,7 +16,7 @@ interface WorkManagerGateway { /** * Enqueue unique work request to start download worker to monitor the download transfers as a foreground service */ - fun enqueueDownloadsWorkerRequest() + suspend fun enqueueDownloadsWorkerRequest() /** * Queue a one time work request of camera upload to upload immediately. diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultTransfersRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultTransfersRepository.kt index 3c93dbad6b0..68a04d75e7f 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/DefaultTransfersRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/DefaultTransfersRepository.kt @@ -457,7 +457,7 @@ internal class DefaultTransfersRepository @Inject constructor( workerManagerGateway.enqueueDeleteOldestCompletedTransfersWorkRequest() } - override fun startDownloadWorker() { + override suspend fun startDownloadWorker() = withContext(ioDispatcher) { workerManagerGateway.enqueueDownloadsWorkerRequest() } diff --git a/data/src/main/java/mega/privacy/android/data/worker/NewMediaWorker.kt b/data/src/main/java/mega/privacy/android/data/worker/NewMediaWorker.kt index a8be2d1b6c5..8702e2c7406 100644 --- a/data/src/main/java/mega/privacy/android/data/worker/NewMediaWorker.kt +++ b/data/src/main/java/mega/privacy/android/data/worker/NewMediaWorker.kt @@ -12,6 +12,7 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import mega.privacy.android.data.facade.debugWorkInfo import mega.privacy.android.domain.usecase.camerauploads.IsCameraUploadsEnabledUseCase import mega.privacy.android.domain.usecase.workers.StartCameraUploadUseCase import timber.log.Timber @@ -46,6 +47,8 @@ internal class NewMediaWorker @AssistedInject constructor( if (isForce || isQueuedOrRunning(workManager) ) { + workManager.debugWorkInfo() + val photoCheckBuilder = OneTimeWorkRequest.Builder(NewMediaWorker::class.java) photoCheckBuilder.setConstraints( diff --git a/data/src/test/java/mega/privacy/android/data/repository/DefaultTransfersRepositoryTest.kt b/data/src/test/java/mega/privacy/android/data/repository/DefaultTransfersRepositoryTest.kt index fa64b651440..d4a9f0602f4 100644 --- a/data/src/test/java/mega/privacy/android/data/repository/DefaultTransfersRepositoryTest.kt +++ b/data/src/test/java/mega/privacy/android/data/repository/DefaultTransfersRepositoryTest.kt @@ -1087,10 +1087,11 @@ class DefaultTransfersRepositoryTest { @TestInstance(TestInstance.Lifecycle.PER_CLASS) inner class WorkerTests { @Test - fun `test that workerManagerGateway enqueueDownloadsWorkerRequest is called when startDownloadWorker is called`() { - underTest.startDownloadWorker() - verify(workerManagerGateway).enqueueDownloadsWorkerRequest() - } + fun `test that workerManagerGateway enqueueDownloadsWorkerRequest is called when startDownloadWorker is called`() = + runTest { + underTest.startDownloadWorker() + verify(workerManagerGateway).enqueueDownloadsWorkerRequest() + } @ParameterizedTest @EnumSource(WorkInfo.State::class) diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/repository/TransferRepository.kt b/domain/src/main/kotlin/mega/privacy/android/domain/repository/TransferRepository.kt index 3b240407f58..cc39d38c347 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/repository/TransferRepository.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/repository/TransferRepository.kt @@ -253,7 +253,7 @@ interface TransferRepository { /** * Starts the download worker to monitor the download transfers as a foreground service */ - fun startDownloadWorker() + suspend fun startDownloadWorker() /** * Monitors transfers finished. diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/transfers/downloads/StartDownloadWorkerUseCase.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/transfers/downloads/StartDownloadWorkerUseCase.kt index a9b18d99f0c..4071e7c99b4 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/transfers/downloads/StartDownloadWorkerUseCase.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/transfers/downloads/StartDownloadWorkerUseCase.kt @@ -16,5 +16,5 @@ class StartDownloadWorkerUseCase @Inject constructor( * Invoke. * */ - operator fun invoke() = transferRepository.startDownloadWorker() -} \ No newline at end of file + suspend operator fun invoke() = transferRepository.startDownloadWorker() +}