Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package com.duckduckgo.remote.messaging.impl.ui

import android.content.Context
import android.content.Intent
import android.os.SystemClock
import androidx.lifecycle.LifecycleOwner
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver
import com.duckduckgo.app.onboarding.OnboardingFlowChecker
import com.duckduckgo.app.settings.db.SettingsDataStore
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.navigation.api.GlobalActivityStarter
Expand Down Expand Up @@ -50,6 +52,7 @@ interface RemoteMessageModalSurfaceEvaluator
class RemoteMessageModalSurfaceEvaluatorImpl @Inject constructor(
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
private val remoteMessagingRepository: RemoteMessagingRepository,
private val settingsDataStore: SettingsDataStore,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After moving this class out of app module I cannot use the SettingsDataStore anymore.

private val globalActivityStarter: GlobalActivityStarter,
private val dispatchers: DispatcherProvider,
private val applicationContext: Context,
Expand All @@ -74,17 +77,38 @@ class RemoteMessageModalSurfaceEvaluatorImpl @Inject constructor(
return@withContext
}

// TODO ANA: This is now called all the time. Update!
if (!hasMetBackgroundTimeThreshold()) {
return@withContext
}

val message = remoteMessagingRepository.message() ?: return@withContext

if (message.surfaces.contains(Surface.MODAL)) {
val intent = globalActivityStarter.startIntent(
applicationContext,
ModalSurfaceActivityFromMessageId(message.id, message.content.messageType),
) ?: return@withContext

intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
applicationContext.startActivity(intent)
withContext(dispatchers.main()) {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
applicationContext.startActivity(intent)
}
}
}
}

private fun hasMetBackgroundTimeThreshold(): Boolean {
if (!settingsDataStore.hasBackgroundTimestampRecorded()) {
return false
}

val backgroundTimestamp = settingsDataStore.appBackgroundedTimestamp
// Using elapsed real time as this is how it's saved in the data store.
val currentTimestamp = SystemClock.elapsedRealtime()
return (currentTimestamp - backgroundTimestamp) >= BACKGROUND_THRESHOLD_MILLIS
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Race condition with background timestamp being cleared concurrently

The hasMetBackgroundTimeThreshold() function reads appBackgroundedTimestamp from settingsDataStore, but this timestamp is cleared by AutomaticDataClearer.onAppForegroundedAsync() when the app is foregrounded. Both MainProcessLifecycleObserver.onResume and BrowserLifecycleObserver.onOpen trigger async coroutines that run on the IO dispatcher, creating a race condition. If AutomaticDataClearer clears the timestamp before this code reads it, hasBackgroundTimestampRecorded() returns false and the modal never displays, even if the app was in the background for 4+ hours.


Please tell me if this was useful or not with a 👍 or 👎.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice that value was cleared 👍 Will update.

}

companion object {
private const val BACKGROUND_THRESHOLD_MILLIS = 4 * 60 * 60 * 1000L // 4 hours
}
}
Loading