Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable countly performance metrics (WPB-8842) #3481

Merged
merged 11 commits into from
Oct 2, 2024
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/wire/android/WireApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ class WireApplication : BaseApp() {
dispatcher = Dispatchers.IO
)

AnonymousAnalyticsManagerImpl.applicationOnCreate()

// observe the app visibility state and send AppOpen event if the app goes from the background to the foreground
globalAppScope.launch {
currentScreenManager
Expand Down
12 changes: 6 additions & 6 deletions app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -566,15 +566,15 @@ class WireActivityViewModel @Inject constructor(
}

private fun CurrentScreen.isGlobalDialogAllowed(): Boolean = when (this) {
CurrentScreen.ImportMedia,
CurrentScreen.DeviceManager -> false
is CurrentScreen.ImportMedia,
is CurrentScreen.DeviceManager -> false

CurrentScreen.InBackground,
is CurrentScreen.InBackground,
is CurrentScreen.Conversation,
CurrentScreen.Home,
is CurrentScreen.Home,
is CurrentScreen.OtherUserProfile,
CurrentScreen.AuthRelated,
CurrentScreen.SomeOther -> true
is CurrentScreen.AuthRelated,
is CurrentScreen.SomeOther -> true
}
}

Expand Down
37 changes: 25 additions & 12 deletions app/src/main/kotlin/com/wire/android/util/CurrentScreenManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@

package com.wire.android.util

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import com.ramcosta.composedestinations.spec.DestinationSpec
import com.wire.android.appLogger
import com.wire.android.feature.analytics.AnonymousAnalyticsManagerImpl
import com.wire.android.navigation.toDestination
import com.wire.android.ui.destinations.ConversationScreenDestination
import com.wire.android.ui.destinations.CreateAccountDetailsScreenDestination
Expand All @@ -46,6 +48,7 @@ import com.wire.android.ui.destinations.RegisterDeviceScreenDestination
import com.wire.android.ui.destinations.RemoveDeviceScreenDestination
import com.wire.android.ui.destinations.SelfDevicesScreenDestination
import com.wire.android.ui.destinations.WelcomeScreenDestination
import com.wire.kalium.logger.obfuscateId
import com.wire.kalium.logic.data.id.ConversationId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -67,7 +70,7 @@ class CurrentScreenManager @Inject constructor(
) : DefaultLifecycleObserver,
NavController.OnDestinationChangedListener {

private val currentScreenState = MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther)
private val currentScreenState = MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther())

/**
* An integer that counts up when a screen appears, and counts down when
Expand Down Expand Up @@ -120,12 +123,17 @@ class CurrentScreenManager @Inject constructor(
}

override fun onDestinationChanged(controller: NavController, destination: NavDestination, arguments: Bundle?) {
val currentView = currentScreenState.value.toString()
AnonymousAnalyticsManagerImpl.stopView(currentView)
val currentItem = destination.toDestination()
currentScreenState.value = CurrentScreen.fromDestination(
currentItem,
arguments,
isApplicationVisibleFlow.value
)

val newView = currentScreenState.value.toString()
AnonymousAnalyticsManagerImpl.recordView(newView)
}

companion object {
Expand All @@ -136,30 +144,35 @@ class CurrentScreenManager @Inject constructor(
sealed class CurrentScreen {

// Home Screen is being displayed
object Home : CurrentScreen()
data object Home : CurrentScreen()

// Some Conversation is opened
data class Conversation(val id: ConversationId) : CurrentScreen()
data class Conversation(val id: ConversationId) : CurrentScreen() {
override fun toString(): String = "Conversation(${id.toString().obfuscateId()})"
}

// Another User Profile Screen is opened
data class OtherUserProfile(val id: ConversationId) : CurrentScreen()
data class OtherUserProfile(val id: ConversationId) : CurrentScreen() {
override fun toString(): String = "OtherUserProfile(${id.toString().obfuscateId()})"
}

// Import media screen is opened
object ImportMedia : CurrentScreen()
data object ImportMedia : CurrentScreen()

// SelfDevices screen is opened
object DeviceManager : CurrentScreen()
data object DeviceManager : CurrentScreen()

// Auth related screen is opened
object AuthRelated : CurrentScreen()
data class AuthRelated(val route: String?) : CurrentScreen()

// Some other screen is opened, kinda "do nothing screen"
object SomeOther : CurrentScreen()
data class SomeOther(val route: String? = null) : CurrentScreen()

// App is in background (screen is turned off, or covered by another app), non of the screens is visible
object InBackground : CurrentScreen()
data object InBackground : CurrentScreen()

companion object {
@SuppressLint("RestrictedApi")
@Suppress("ComplexMethod")
fun fromDestination(destination: DestinationSpec<*>?, arguments: Bundle?, isAppVisible: Boolean): CurrentScreen {
if (!isAppVisible) {
Expand All @@ -171,7 +184,7 @@ sealed class CurrentScreen {
Conversation(destination.argsFrom(arguments).conversationId)

is OtherUserProfileScreenDestination ->
destination.argsFrom(arguments).conversationId?.let { OtherUserProfile(it) } ?: SomeOther
destination.argsFrom(arguments).conversationId?.let { OtherUserProfile(it) } ?: SomeOther(destination.baseRoute)

is ImportMediaScreenDestination -> ImportMedia

Expand All @@ -189,9 +202,9 @@ sealed class CurrentScreen {
is E2EIEnrollmentScreenDestination,
is E2eiCertificateDetailsScreenDestination,
is RegisterDeviceScreenDestination,
is RemoveDeviceScreenDestination -> AuthRelated
is RemoveDeviceScreenDestination -> AuthRelated(destination.baseRoute)

else -> SomeOther
else -> SomeOther(destination?.baseRoute)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class WireNotificationManagerTest {
fun givenNotAuthenticatedUser_whenObserveCalled_thenNothingHappenAndCallNotificationHides() =
runTestWithCancellation(dispatcherProvider.main()) {
val (arrangement, manager) = Arrangement()
.withCurrentScreen(CurrentScreen.SomeOther)
.withCurrentScreen(CurrentScreen.SomeOther())
.arrange()

manager.observeNotificationsAndCallsWhileRunning(listOf(), this)
Expand All @@ -152,7 +152,7 @@ class WireNotificationManagerTest {
val (arrangement, manager) = Arrangement()
.withSpecificUserSession(userId = userId, incomingCalls = incomingCalls)
.withMessageNotifications(listOf())
.withCurrentScreen(CurrentScreen.SomeOther)
.withCurrentScreen(CurrentScreen.SomeOther())
.withCurrentUserSession(CurrentSessionResult.Success(AccountInfo.Valid(userId)))
.arrange()

Expand All @@ -174,7 +174,7 @@ class WireNotificationManagerTest {
.withSpecificUserSession(userId = user1, incomingCalls = listOf())
.withSpecificUserSession(userId = user2, incomingCalls = incomingCalls)
.withMessageNotifications(listOf())
.withCurrentScreen(CurrentScreen.SomeOther)
.withCurrentScreen(CurrentScreen.SomeOther())
.withCurrentUserSession(CurrentSessionResult.Success(provideAccountInfo(user1.value)))
.arrange()

Expand All @@ -198,7 +198,7 @@ class WireNotificationManagerTest {
)
)
)
.withCurrentScreen(CurrentScreen.SomeOther).arrange()
.withCurrentScreen(CurrentScreen.SomeOther()).arrange()

manager.observeNotificationsAndCallsWhileRunning(listOf(), this)
runCurrent()
Expand Down Expand Up @@ -249,7 +249,7 @@ class WireNotificationManagerTest {
.withMessageNotifications(messageNotifications)
.withIncomingCalls(listOf())
.withOutgoingCalls(listOf())
.withCurrentScreen(CurrentScreen.SomeOther)
.withCurrentScreen(CurrentScreen.SomeOther())
.arrange()

manager.observeNotificationsAndCallsWhileRunning(listOf(provideUserId(TestUser.SELF_USER.id.value)), this)
Expand Down Expand Up @@ -960,7 +960,7 @@ class WireNotificationManagerTest {
)
.withIncomingCalls(listOf())
.withOutgoingCalls(listOf())
.withCurrentScreen(CurrentScreen.SomeOther)
.withCurrentScreen(CurrentScreen.SomeOther())
.withObserveE2EIRequired(E2EIRequiredResult.NoGracePeriod.Create)
.arrange()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ class WireActivityViewModelTest {
val (_, viewModel) = Arrangement()
.withNoCurrentSession()
.withNewClient(NewClientResult.InCurrentAccount(listOf(TestClient.CLIENT), USER_ID))
.withCurrentScreen(MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther))
.withCurrentScreen(MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther()))
.arrange()

assertEquals(
Expand All @@ -479,7 +479,7 @@ class WireActivityViewModelTest {
val (_, viewModel) = Arrangement()
.withNoCurrentSession()
.withNewClient(NewClientResult.InOtherAccount(listOf(TestClient.CLIENT), USER_ID, "name", "handle"))
.withCurrentScreen(MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther))
.withCurrentScreen(MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther()))
.arrange()

assertEquals(
Expand All @@ -495,7 +495,7 @@ class WireActivityViewModelTest {

@Test
fun `given newClient is registered when current screen does not allow dialog, then remember NewClient dialog state`() = runTest {
val currentScreenFlow = MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther)
val currentScreenFlow = MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther())
val newClientFlow = MutableSharedFlow<NewClientResult>()
val (_, viewModel) = Arrangement()
.withNoCurrentSession()
Expand All @@ -513,7 +513,7 @@ class WireActivityViewModelTest {

@Test
fun `given newClient is registered when current screen changed to ImportMedea, then remember NewClient dialog state`() = runTest {
val currentScreenFlow = MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther)
val currentScreenFlow = MutableStateFlow<CurrentScreen>(CurrentScreen.SomeOther())
val (_, viewModel) = Arrangement()
.withNoCurrentSession()
.withNewClient(NewClientResult.InCurrentAccount(listOf(TestClient.CLIENT), USER_ID))
Expand Down Expand Up @@ -628,7 +628,7 @@ class WireActivityViewModelTest {
every { observeScreenshotCensoringConfigUseCaseProviderFactory.create(any()).observeScreenshotCensoringConfig } returns
observeScreenshotCensoringConfigUseCase
coEvery { observeScreenshotCensoringConfigUseCase() } returns flowOf(ObserveScreenshotCensoringConfigResult.Disabled)
coEvery { currentScreenManager.observeCurrentScreen(any()) } returns MutableStateFlow(CurrentScreen.SomeOther)
coEvery { currentScreenManager.observeCurrentScreen(any()) } returns MutableStateFlow(CurrentScreen.SomeOther())
coEvery { globalDataStore.selectedThemeOptionFlow() } returns flowOf(ThemeOption.LIGHT)
coEvery { observeIfE2EIRequiredDuringLoginUseCaseProviderFactory.create(any()).observeIfE2EIIsRequiredDuringLogin() } returns
flowOf(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class CommonTopAppBarViewModelTest {
.withOngoingCall(isMuted = false)
.withoutOutgoingCall()
.withoutIncomingCall()
.withCurrentScreen(CurrentScreen.SomeOther)
.withCurrentScreen(CurrentScreen.SomeOther())
.withSyncState(SyncState.Waiting)
.arrange()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,7 @@ class RecordAudioViewModelTest {
coEvery { generateAudioFileWithEffects(any(), any(), any()) } returns Unit

coEvery { currentScreenManager.observeCurrentScreen(any()) } returns MutableStateFlow(
CurrentScreen.Conversation(
id = DUMMY_CALL.conversationId
)
CurrentScreen.Conversation(id = DUMMY_CALL.conversationId)
)

coEvery { recordAudioMessagePlayer.audioMessageStateFlow } returns flowOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,29 @@ object AnonymousAnalyticsManagerImpl : AnonymousAnalyticsManager {
Log.w(TAG, "Calling isAnalyticsInitialized with a null recorder.")
false
}

override fun recordView(screen: String) {
coroutineScope.launch {
mutex.withLock {
if (!isAnonymousUsageDataEnabled) return@withLock
anonymousAnalyticsRecorder?.recordView(screen)
}
}
}

override fun stopView(screen: String) {
coroutineScope.launch {
mutex.withLock {
if (!isAnonymousUsageDataEnabled) return@withLock
anonymousAnalyticsRecorder?.stopView(screen)
}
}
}

override fun applicationOnCreate() {
if (!isAnonymousUsageDataEnabled) return

anonymousAnalyticsRecorder?.applicationOnCreate()
?: Log.w(TAG, "Calling applicationOnCreate with a null recorder.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.wire.android.feature.analytics

import android.app.Activity
import android.app.Application
import android.content.Context
import com.wire.android.feature.analytics.model.AnalyticsEvent
import com.wire.android.feature.analytics.model.AnalyticsEventConstants
Expand All @@ -42,8 +43,12 @@ class AnonymousAnalyticsRecorderImpl : AnonymousAnalyticsRecorder {
)
.enableTemporaryDeviceIdMode() // Nothing is sent until a proper ID is placed
.setLoggingEnabled(analyticsSettings.enableDebugLogging)
countlyConfig.apm.enableAppStartTimeTracking()
countlyConfig.apm.enableForegroundBackgroundTracking()
countlyConfig.setApplication(context.applicationContext as Application)

Countly.sharedInstance().init(countlyConfig)
Countly.sharedInstance().consent().giveConsent(arrayOf("apm"))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Runtime consent given to the SDK for performance metrics, only enabled if analytics enabled.


val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val globalSegmentations = mapOf<String, Any>(
Expand Down Expand Up @@ -107,4 +112,18 @@ class AnonymousAnalyticsRecorderImpl : AnonymousAnalyticsRecorder {
}

override fun isAnalyticsInitialized(): Boolean = Countly.sharedInstance().isInitialized

override fun applicationOnCreate() {
if (isConfigured) return

Countly.applicationOnCreate()
}

override fun recordView(screen: String) {
Countly.sharedInstance().views().startAutoStoppedView(screen)
}

override fun stopView(screen: String) {
Countly.sharedInstance().views().stopViewWithName(screen)
}
}
Loading
Loading