Skip to content

Commit

Permalink
fix: not able to open some conversations [WPB-11325]
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk committed Dec 9, 2024
1 parent 8d9b34a commit 102d388
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.notification.NotificationConstants.INCOMING_CALL_ID_PREFIX
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.call.Call
import com.wire.kalium.logic.data.call.CallStatus
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -54,9 +57,10 @@ import javax.inject.Singleton
@Singleton
@Suppress("TooManyFunctions")
class CallNotificationManager @Inject constructor(
private val context: Context,
context: Context,
dispatcherProvider: DispatcherProvider,
val builder: CallNotificationBuilder,
@KaliumCoreLogic private val coreLogic: CoreLogic,
) {

private val notificationManager = NotificationManagerCompat.from(context)
Expand All @@ -83,8 +87,19 @@ class CallNotificationManager @Inject constructor(
hideOutdatedIncomingCallNotifications(allCurrentCalls)
// show current incoming call notifications
appLogger.i("$TAG: showing ${newCalls.size} new incoming calls (all incoming calls: ${allCurrentCalls.size})")

val currentSessionId = (coreLogic.getGlobalScope().session.currentSession() as? CurrentSessionResult.Success)?.let {
if (it.accountInfo.isValid()) it.accountInfo.userId else null
}
newCalls.forEach { data ->
showIncomingCallNotification(data)
/**
* For now only show full screen intent for current session, as if shown for another session it will switch to that
* session and the user won't know that he/she is receiving a call as a different account.
* For calls that are not for the current session it will show the notification as a heads up notification.
* In the future we can implement showing on the incoming call screen as what account the user will answer
* or even give them the option to change it themselves on that screen.
*/
showIncomingCallNotification(data = data, asFullScreenIntent = currentSessionId == data.userId)
}
}
}
Expand Down Expand Up @@ -139,14 +154,14 @@ class CallNotificationManager @Inject constructor(

@SuppressLint("MissingPermission")
@VisibleForTesting
internal fun showIncomingCallNotification(data: CallNotificationData) {
internal fun showIncomingCallNotification(data: CallNotificationData, asFullScreenIntent: Boolean) {
appLogger.i(
"$TAG: showing incoming call notification for user ${data.userId.toLogString()}" +
" and conversation ${data.conversationId.toLogString()}"
)
val tag = NotificationConstants.getIncomingCallTag(data.userId.toString())
val id = NotificationConstants.getIncomingCallId(data.userId.toString(), data.conversationId.toString())
val notification = builder.getIncomingCallNotification(data)
val notification = builder.getIncomingCallNotification(data, asFullScreenIntent)
notificationManager.notify(tag, id, notification)
}

Expand Down Expand Up @@ -189,12 +204,11 @@ class CallNotificationBuilder @Inject constructor(
)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setFullScreenIntent(outgoingCallPendingIntent(context, conversationIdString), true)
.setContentIntent(outgoingCallPendingIntent(context, conversationIdString))
.build()
}

fun getIncomingCallNotification(data: CallNotificationData): Notification {
fun getIncomingCallNotification(data: CallNotificationData, asFullScreenIntent: Boolean): Notification {
val conversationIdString = data.conversationId.toString()
val userIdString = data.userId.toString()
val title = getNotificationTitle(data)
Expand All @@ -220,8 +234,14 @@ class CallNotificationBuilder @Inject constructor(
)
.setVibrate(VIBRATE_PATTERN)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setFullScreenIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString, userIdString), true)
.setContentIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString, userIdString))
.let {
if (asFullScreenIntent) {
it.setFullScreenIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString, userIdString), true)
} else {
it
}
}
.build()

// Added FLAG_INSISTENT so the ringing sound repeats itself until an action is done.
Expand Down Expand Up @@ -255,7 +275,6 @@ class CallNotificationBuilder @Inject constructor(
)
)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setFullScreenIntent(openOngoingCallPendingIntent(context, conversationIdString), true)
.setContentIntent(openOngoingCallPendingIntent(context, conversationIdString))
.build()
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialog
import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialogState
import com.wire.android.util.CurrentScreenManager
import com.wire.android.util.LocalSyncStateObserver
import com.wire.android.util.SwitchAccountObserver
import com.wire.android.util.SyncStateObserver
import com.wire.android.util.debug.FeatureVisibilityFlags
import com.wire.android.util.debug.LocalFeatureVisibilityFlags
Expand All @@ -135,6 +136,9 @@ class WireActivity : AppCompatActivity() {
@Inject
lateinit var lockCodeTimeManager: Lazy<LockCodeTimeManager>

@Inject
lateinit var switchAccountObserver: SwitchAccountObserver

private val viewModel: WireActivityViewModel by viewModels()

private val featureFlagNotificationViewModel: FeatureFlagNotificationViewModel by viewModels()
Expand Down Expand Up @@ -346,6 +350,19 @@ class WireActivity : AppCompatActivity() {
navigator.navController.removeOnDestinationChangedListener(currentScreenManager)
}
}

DisposableEffect(switchAccountObserver, navigator) {
NavigationSwitchAccountActions {
lifecycleScope.launch(Dispatchers.Main) {
navigator.navigate(it)
}
}.let {
switchAccountObserver.register(it)
onDispose {
switchAccountObserver.unregister(it)
}
}
}
}

@Composable
Expand Down
144 changes: 49 additions & 95 deletions app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,9 @@ import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSo
import com.wire.kalium.util.DateTimeUtil.toIsoDateTimeString
import dagger.Lazy
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
Expand Down Expand Up @@ -131,24 +130,19 @@ class WireActivityViewModel @Inject constructor(
private val _observeSyncFlowState: MutableStateFlow<SyncState?> = MutableStateFlow(null)
val observeSyncFlowState: StateFlow<SyncState?> = _observeSyncFlowState

private val userIdDeferred: Deferred<UserId?> = viewModelScope.async(dispatchers.io()) {
currentSessionFlow.get().invoke()
.distinctUntilChanged()
.map { result ->
if (result is CurrentSessionResult.Success) {
if (result.accountInfo.isValid()) {
result.accountInfo.userId
} else {
null
}
} else {
null
}
}
.distinctUntilChanged()
.flowOn(dispatchers.io())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1).first()
}
private val observeCurrentAccountInfo: SharedFlow<AccountInfo?> = currentSessionFlow.get().invoke()
.map { (it as? CurrentSessionResult.Success)?.accountInfo }
.distinctUntilChanged()
.flowOn(dispatchers.io())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)

private val observeCurrentValidUserId: SharedFlow<UserId?> = observeCurrentAccountInfo
.map {
if (it?.isValid() == true) it.userId else null
}
.distinctUntilChanged()
.flowOn(dispatchers.io())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)

init {
observeSyncState()
Expand All @@ -159,21 +153,10 @@ class WireActivityViewModel @Inject constructor(
observeLogoutState()
}

@Suppress("TooGenericExceptionCaught")
private fun shouldEnrollToE2ei() = viewModelScope.async(dispatchers.io()) {
try {
val userId = userIdDeferred.await()
if (userId != null) {
observeIfE2EIRequiredDuringLoginUseCaseProviderFactory.create(userId)
.observeIfE2EIIsRequiredDuringLogin().first() ?: false
} else {
false
}
} catch (e: NullPointerException) {
appLogger.e("Error while observing E2EI state: $e")
false
}
}
private suspend fun shouldEnrollToE2ei(): Boolean = observeCurrentValidUserId.first()?.let {
observeIfE2EIRequiredDuringLoginUseCaseProviderFactory.create(it)
.observeIfE2EIIsRequiredDuringLogin().first() ?: false
} ?: false

private fun observeAppThemeState() {
viewModelScope.launch(dispatchers.io()) {
Expand All @@ -185,33 +168,28 @@ class WireActivityViewModel @Inject constructor(
}
}

@Suppress("TooGenericExceptionCaught")
private fun observeSyncState() {
viewModelScope.launch(dispatchers.io()) {
try {
val userId = userIdDeferred.await()
if (userId != null) {
observeSyncStateUseCaseProviderFactory.create(userId).observeSyncState()
} else {
flowOf(null)
.distinctUntilChanged()
.collect { _observeSyncFlowState.emit(it) }
observeCurrentValidUserId
.flatMapLatest { userId ->
userId?.let {
observeSyncStateUseCaseProviderFactory.create(userId).observeSyncState()
} ?: flowOf(null)
}
.distinctUntilChanged()
.flowOn(dispatchers.io())
.collect {
_observeSyncFlowState.emit(it)
}
} catch (e: NullPointerException) {
appLogger.e("Error while observing sync state: $e")
}
}
}

private fun observeLogoutState() {
viewModelScope.launch(dispatchers.io()) {
currentSessionFlow.get().invoke()
.distinctUntilChanged()
observeCurrentAccountInfo
.collect {
if (it is CurrentSessionResult.Success) {
if (it.accountInfo.isValid().not()) {
handleInvalidSession((it.accountInfo as AccountInfo.Invalid).logoutReason)
}
if (it is AccountInfo.Invalid) {
handleInvalidSession(it.logoutReason)
}
}
}
Expand Down Expand Up @@ -244,43 +222,29 @@ class WireActivityViewModel @Inject constructor(
}
}

@Suppress("TooGenericExceptionCaught")
private fun observeScreenshotCensoringConfigState() {
viewModelScope.launch(dispatchers.io()) {
try {
val userId = userIdDeferred.await()
if (userId != null) {
observeScreenshotCensoringConfigUseCaseProviderFactory.create(userId)
.observeScreenshotCensoringConfig().collect { result ->
globalAppState = globalAppState.copy(
screenshotCensoringEnabled = result is ObserveScreenshotCensoringConfigResult.Enabled
)
}
} else {
globalAppState = globalAppState.copy(
screenshotCensoringEnabled = false
)
observeCurrentValidUserId
.flatMapLatest { currentValidUserId ->
currentValidUserId?.let {
observeScreenshotCensoringConfigUseCaseProviderFactory.create(it)
.observeScreenshotCensoringConfig()
.map { result ->
result is ObserveScreenshotCensoringConfigResult.Enabled
}
} ?: flowOf(false)
}
.collect {
globalAppState = globalAppState.copy(screenshotCensoringEnabled = it)
}
} catch (exception: NullPointerException) {
globalAppState = globalAppState.copy(
screenshotCensoringEnabled = false
)
}
}
}

suspend fun initialAppState(): InitialAppState {
val shouldMigrate = viewModelScope.async(dispatchers.io()) {
shouldMigrate()
}
val shouldLogin = viewModelScope.async(dispatchers.io()) {
shouldLogIn()
}
val shouldEnrollToE2ei = shouldEnrollToE2ei()
return when {
shouldMigrate.await() -> InitialAppState.NOT_MIGRATED
shouldLogin.await() -> InitialAppState.NOT_LOGGED_IN
shouldEnrollToE2ei.await() -> InitialAppState.ENROLL_E2EI
suspend fun initialAppState(): InitialAppState = withContext(dispatchers.io()) {
when {
shouldMigrate() -> InitialAppState.NOT_MIGRATED
shouldLogIn() -> InitialAppState.NOT_LOGGED_IN
shouldEnrollToE2ei() -> InitialAppState.ENROLL_E2EI
else -> InitialAppState.LOGGED_IN
}
}
Expand Down Expand Up @@ -517,17 +481,7 @@ class WireActivityViewModel @Inject constructor(
globalAppState = globalAppState.copy(conversationJoinedDialog = null)
}

private suspend fun shouldLogIn(): Boolean = !hasValidCurrentSession()

private suspend fun hasValidCurrentSession(): Boolean =
// TODO: the usage of currentSessionFlow is a temporary solution, it should be replaced with a proper solution
currentSessionFlow.get().invoke().first().let {
when (it) {
is CurrentSessionResult.Failure.Generic -> false
CurrentSessionResult.Failure.SessionNotFound -> false
is CurrentSessionResult.Success -> true
}
}
private suspend fun shouldLogIn(): Boolean = observeCurrentValidUserId.first() == null

private suspend fun shouldMigrate(): Boolean = migrationManager.get().shouldMigrate()

Expand Down
10 changes: 9 additions & 1 deletion app/src/main/kotlin/com/wire/android/ui/calling/CallActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@ import android.os.Build
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.wire.android.util.SwitchAccountObserver
import androidx.lifecycle.lifecycleScope
import com.wire.android.ui.AppLockActivity
import com.wire.kalium.logic.data.id.QualifiedIdMapperImpl
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
abstract class CallActivity : AppCompatActivity() {

@Inject
lateinit var switchAccountObserver: SwitchAccountObserver

companion object {
const val EXTRA_CONVERSATION_ID = "conversation_id"
const val EXTRA_USER_ID = "user_id"
Expand All @@ -40,7 +48,7 @@ abstract class CallActivity : AppCompatActivity() {
fun switchAccountIfNeeded(userId: String?) {
userId?.let {
qualifiedIdMapper.fromStringToQualifiedID(it).run {
callActivityViewModel.switchAccountIfNeeded(this)
callActivityViewModel.switchAccountIfNeeded(userId = this, actions = switchAccountObserver)
}
}
}
Expand Down
Loading

0 comments on commit 102d388

Please sign in to comment.