diff --git a/app/src/main/kotlin/com/wire/android/WireApplication.kt b/app/src/main/kotlin/com/wire/android/WireApplication.kt index d67de8e4c97..58ed10c4a55 100644 --- a/app/src/main/kotlin/com/wire/android/WireApplication.kt +++ b/app/src/main/kotlin/com/wire/android/WireApplication.kt @@ -32,6 +32,7 @@ import com.wire.android.datastore.UserDataStoreProvider import com.wire.android.debug.DatabaseProfilingManager import com.wire.android.di.ApplicationScope import com.wire.android.di.KaliumCoreLogic +import com.wire.android.feature.analytics.AnonymousAnalyticsManager import com.wire.android.feature.analytics.AnonymousAnalyticsManagerImpl import com.wire.android.feature.analytics.AnonymousAnalyticsRecorderImpl import com.wire.android.feature.analytics.globalAnalyticsManager @@ -48,12 +49,15 @@ import com.wire.kalium.logger.KaliumLogLevel import com.wire.kalium.logger.KaliumLogger import com.wire.kalium.logic.CoreLogger import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.feature.session.CurrentSessionResult import dagger.Lazy import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -93,6 +97,9 @@ class WireApplication : BaseApp() { @Inject lateinit var databaseProfilingManager: DatabaseProfilingManager + @Inject + lateinit var analyticsManager: Lazy + override val workManagerConfiguration: Configuration get() = Configuration.Builder() .setWorkerFactory(wireWorkerFactory.get()) @@ -121,9 +128,22 @@ class WireApplication : BaseApp() { appLogger.i("$TAG global observers") globalObserversManager.get().observe() + + observeRecentlyEndedCall() } } + private suspend fun observeRecentlyEndedCall() { + coreLogic.get().getGlobalScope().session.currentSessionFlow().filterIsInstance(CurrentSessionResult.Success::class) + .filter { session -> session.accountInfo.isValid() } + .flatMapLatest { session -> + coreLogic.get().getSessionScope(session.accountInfo.userId).calls.observeRecentlyEndedCallMetadata() + } + .collect { metadata -> + analyticsManager.get().sendEvent(AnalyticsEvent.RecentlyEndedCallEvent(metadata)) + } + } + private fun enableStrictMode() { if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy( diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt index 803e627db87..99d5a287ad4 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt @@ -19,8 +19,6 @@ package com.wire.android.di.accountScoped import com.wire.android.di.CurrentAccount import com.wire.android.di.KaliumCoreLogic -import dagger.Module -import dagger.Provides import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.call.CallsScope @@ -40,6 +38,8 @@ import com.wire.kalium.logic.feature.call.usecase.TurnLoudSpeakerOnUseCase import com.wire.kalium.logic.feature.call.usecase.UnMuteCallUseCase import com.wire.kalium.logic.feature.call.usecase.video.SetVideoSendStateUseCase import com.wire.kalium.logic.feature.call.usecase.video.UpdateVideoStateUseCase +import dagger.Module +import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.scopes.ViewModelScoped diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt index fc173d079cb..ddcf9d3f0b4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt @@ -121,7 +121,7 @@ class WireActivityViewModel @Inject constructor( private val observeScreenshotCensoringConfigUseCaseProviderFactory: ObserveScreenshotCensoringConfigUseCaseProvider.Factory, private val globalDataStore: Lazy, private val observeIfE2EIRequiredDuringLoginUseCaseProviderFactory: ObserveIfE2EIRequiredDuringLoginUseCaseProvider.Factory, - private val workManager: Lazy, + private val workManager: Lazy ) : ViewModel() { var globalAppState: GlobalAppState by mutableStateOf(GlobalAppState()) diff --git a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt index f7070a642bf..c2744f0635f 100644 --- a/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/WireActivityViewModelTest.kt @@ -53,6 +53,7 @@ import com.wire.kalium.logic.data.auth.AccountInfo import com.wire.kalium.logic.data.auth.PersistentWebSocketStatus import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.call.CallStatus +import com.wire.kalium.logic.data.call.RecentlyEndedCallMetadata import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID @@ -974,6 +975,28 @@ class WireActivityViewModelTest { callerTeamName = "team1" ) + val recentlyEndedCallMetadata = RecentlyEndedCallMetadata( + callEndReason = 1, + callDetails = RecentlyEndedCallMetadata.CallDetails( + isCallScreenShare = false, + screenShareDurationInSeconds = 20L, + callScreenShareUniques = 5, + isOutgoingCall = true, + callDurationInSeconds = 100L, + callParticipantsCount = 5, + conversationServices = 1, + callAVSwitchToggle = false, + callVideoEnabled = false + ), + conversationDetails = RecentlyEndedCallMetadata.ConversationDetails( + conversationType = Conversation.Type.ONE_ON_ONE, + conversationSize = 5, + conversationGuests = 2, + conversationGuestsPro = 1 + ), + isTeamMember = true + ) + fun invalidAccountInfo(logoutReason: LogoutReason): AccountInfo.Invalid = AccountInfo.Invalid(USER_ID, logoutReason) } } diff --git a/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt index b08eb4585b5..b133c02d172 100644 --- a/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt +++ b/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt @@ -17,6 +17,21 @@ */ package com.wire.android.feature.analytics.model +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_AV_SWITCH_TOGGLE +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CALL_DIRECTION +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CALL_DURATION +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CALL_PARTICIPANTS +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CALL_SCREEN_SHARE +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CALL_VIDEO +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_GUESTS +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_GUESTS_PRO +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_SERVICES +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_SIZE +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_TYPE +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_END_REASON +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_IS_TEAM_MEMBER +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_UNIQUE_SCREEN_SHARE import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_QUALITY_REVIEW_IGNORE_REASON import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_QUALITY_REVIEW_IGNORE_REASON_KEY import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_QUALITY_REVIEW_LABEL_ANSWERED @@ -29,8 +44,6 @@ import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CLICKED_ import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CLICKED_PERSONAL_MIGRATION_CTA_EVENT import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CONTRIBUTED_LOCATION import com.wire.android.feature.analytics.model.AnalyticsEventConstants.MESSAGE_ACTION_KEY -import com.wire.android.feature.analytics.model.AnalyticsEventConstants.QR_CODE_SEGMENTATION_USER_TYPE_PERSONAL -import com.wire.android.feature.analytics.model.AnalyticsEventConstants.QR_CODE_SEGMENTATION_USER_TYPE_TEAM import com.wire.android.feature.analytics.model.AnalyticsEventConstants.MIGRATION_DOT_ACTIVE import com.wire.android.feature.analytics.model.AnalyticsEventConstants.MODAL_BACK_TO_WIRE_CLICKED import com.wire.android.feature.analytics.model.AnalyticsEventConstants.MODAL_CONTINUE_CLICKED @@ -40,8 +53,12 @@ import com.wire.android.feature.analytics.model.AnalyticsEventConstants.MODAL_TE import com.wire.android.feature.analytics.model.AnalyticsEventConstants.PERSONAL_TEAM_CREATION_FLOW_CANCELLED import com.wire.android.feature.analytics.model.AnalyticsEventConstants.PERSONAL_TEAM_CREATION_FLOW_COMPLETED import com.wire.android.feature.analytics.model.AnalyticsEventConstants.PERSONAL_TEAM_CREATION_FLOW_STARTED_EVENT +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.QR_CODE_SEGMENTATION_USER_TYPE_PERSONAL +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.QR_CODE_SEGMENTATION_USER_TYPE_TEAM import com.wire.android.feature.analytics.model.AnalyticsEventConstants.STEP_MODAL_CREATE_TEAM import com.wire.android.feature.analytics.model.AnalyticsEventConstants.USER_PROFILE_OPENED +import com.wire.kalium.logic.data.call.RecentlyEndedCallMetadata +import com.wire.kalium.logic.data.conversation.Conversation interface AnalyticsEvent { /** @@ -129,6 +146,45 @@ interface AnalyticsEvent { } } + data class RecentlyEndedCallEvent(val metadata: RecentlyEndedCallMetadata) : AnalyticsEvent { + override val key: String = CALLING_ENDED + + override fun toSegmentation(): Map { + return mapOf( + CALLING_ENDED_IS_TEAM_MEMBER to metadata.isTeamMember, + CALLING_ENDED_CALL_SCREEN_SHARE to metadata.callDetails.screenShareDurationInSeconds, + CALLING_ENDED_UNIQUE_SCREEN_SHARE to metadata.callDetails.callScreenShareUniques, + CALLING_ENDED_CALL_DIRECTION to metadata.toCallDirection(), + CALLING_ENDED_CALL_DURATION to metadata.callDetails.callDurationInSeconds, + CALLING_ENDED_CONVERSATION_TYPE to metadata.toConversationType(), + CALLING_ENDED_CONVERSATION_SIZE to metadata.conversationDetails.conversationSize, + CALLING_ENDED_CONVERSATION_GUESTS to metadata.conversationDetails.conversationGuests, + CALLING_ENDED_CONVERSATION_GUESTS_PRO to metadata.conversationDetails.conversationGuestsPro, + CALLING_ENDED_CALL_PARTICIPANTS to metadata.callDetails.callParticipantsCount, + CALLING_ENDED_END_REASON to metadata.callEndReason, + CALLING_ENDED_CONVERSATION_SERVICES to metadata.callDetails.conversationServices, + CALLING_ENDED_AV_SWITCH_TOGGLE to metadata.callDetails.callAVSwitchToggle, + CALLING_ENDED_CALL_VIDEO to metadata.callDetails.callVideoEnabled, + ) + } + + private fun RecentlyEndedCallMetadata.toCallDirection(): String { + return if (callDetails.isOutgoingCall) { + "outgoing" + } else { + "incoming" + } + } + + private fun RecentlyEndedCallMetadata.toConversationType(): String { + return when (conversationDetails.conversationType) { + Conversation.Type.ONE_ON_ONE -> "one_to_one" + Conversation.Type.GROUP -> "group" + else -> throw IllegalStateException("Call should not happen for ${conversationDetails.conversationType}") + } + } + } + /** * Backup */ @@ -336,6 +392,7 @@ object AnalyticsEventConstants { */ const val CALLING_INITIATED = "calling.initiated_call" const val CALLING_JOINED = "calling.joined_call" + const val CALLING_ENDED = "calling.ended_call" const val CALLING_QUALITY_REVIEW = "calling.call_quality_review" const val CALLING_QUALITY_REVIEW_LABEL_KEY = "label" @@ -346,6 +403,24 @@ object AnalyticsEventConstants { const val CALLING_QUALITY_REVIEW_IGNORE_REASON_KEY = "ignore-reason" const val CALLING_QUALITY_REVIEW_IGNORE_REASON = "muted" + /** + * Call ended + */ + const val CALLING_ENDED_IS_TEAM_MEMBER = "is_team_member" + const val CALLING_ENDED_CALL_SCREEN_SHARE = "call_screen_share_duration" + const val CALLING_ENDED_UNIQUE_SCREEN_SHARE = "call_screen_share_unique" + const val CALLING_ENDED_CALL_DIRECTION = "call_direction" + const val CALLING_ENDED_CALL_DURATION = "call_duration" + const val CALLING_ENDED_CONVERSATION_TYPE = "conversation_type" + const val CALLING_ENDED_CONVERSATION_SIZE = "conversation_size" + const val CALLING_ENDED_CONVERSATION_GUESTS = "conversation_guests" + const val CALLING_ENDED_CONVERSATION_GUESTS_PRO = "conversation_guest_pro" + const val CALLING_ENDED_CALL_PARTICIPANTS = "call_participants" + const val CALLING_ENDED_END_REASON = "call_end_reason" + const val CALLING_ENDED_CONVERSATION_SERVICES = "conversation_services" + const val CALLING_ENDED_AV_SWITCH_TOGGLE = "call_av_switch_toggle" + const val CALLING_ENDED_CALL_VIDEO = "call_video" + /** * Backup */ diff --git a/kalium b/kalium index 1ca6dfc988e..91b8319e99d 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 1ca6dfc988eeccf550858620ae1dadf8e49555da +Subproject commit 91b8319e99d83e486751967c8adf4f027d57a82e