From 28050488baf72bfb7f75a746e859c8ed048daaf5 Mon Sep 17 00:00:00 2001
From: Adam Brown <adampsbrown@gmail.com>
Date: Fri, 13 May 2022 08:45:45 +0100
Subject: [PATCH] passing the authentication state from the onboarding and
 tracking sign up after the user has consented to tracking

---
 .../analytics/extensions/SignUpExt.kt         | 31 +++++++++++++
 .../vector/app/features/home/HomeActivity.kt  |  9 ++--
 .../features/home/HomeActivityViewActions.kt  |  3 +-
 .../features/home/HomeActivityViewModel.kt    | 46 +++++++++++++++----
 .../features/home/HomeActivityViewState.kt    |  3 +-
 .../app/features/login/LoginActivity.kt       |  5 +-
 .../onboarding/AuthenticationDescription.kt   |  9 +++-
 .../app/features/onboarding/Login2Variant.kt  |  9 ++--
 .../onboarding/OnboardingViewModel.kt         | 19 +++++---
 .../onboarding/OnboardingViewState.kt         |  2 +-
 .../ftueauth/FtueAuthCombinedLoginFragment.kt |  2 +-
 .../onboarding/ftueauth/FtueAuthVariant.kt    | 14 +++---
 12 files changed, 112 insertions(+), 40 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt

diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt
new file mode 100644
index 0000000000..36ec4f7bb9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.analytics.extensions
+
+import im.vector.app.features.analytics.plan.Signup
+import im.vector.app.features.onboarding.AuthenticationDescription
+
+fun AuthenticationDescription.AuthenticationType.toAnalyticsType() = when (this) {
+    AuthenticationDescription.AuthenticationType.Password -> Signup.AuthenticationType.Password
+    AuthenticationDescription.AuthenticationType.Apple    -> Signup.AuthenticationType.Apple
+    AuthenticationDescription.AuthenticationType.Facebook -> Signup.AuthenticationType.Facebook
+    AuthenticationDescription.AuthenticationType.Github   -> Signup.AuthenticationType.GitHub
+    AuthenticationDescription.AuthenticationType.Gitlab   -> Signup.AuthenticationType.GitLab
+    AuthenticationDescription.AuthenticationType.Google   -> Signup.AuthenticationType.Google
+    AuthenticationDescription.AuthenticationType.SSO      -> Signup.AuthenticationType.SSO
+    AuthenticationDescription.AuthenticationType.Other    -> Signup.AuthenticationType.Other
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 9fe1e00ae7..4ed04561d1 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -56,6 +56,7 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet
 import im.vector.app.features.matrixto.OriginOfMatrixTo
 import im.vector.app.features.navigation.Navigator
 import im.vector.app.features.notifications.NotificationDrawerManager
+import im.vector.app.features.onboarding.AuthenticationDescription
 import im.vector.app.features.permalink.NavigationInterceptor
 import im.vector.app.features.permalink.PermalinkHandler
 import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
@@ -91,7 +92,7 @@ import javax.inject.Inject
 @Parcelize
 data class HomeActivityArgs(
         val clearNotification: Boolean,
-        val accountCreation: Boolean,
+        val authenticationDescription: AuthenticationDescription? = null,
         val hasExistingSession: Boolean = false,
         val inviteNotificationRoomId: String? = null
 ) : Parcelable
@@ -248,7 +249,7 @@ class HomeActivity :
         if (isFirstCreation()) {
             handleIntent(intent)
         }
-        homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
+        homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted(args?.authenticationDescription))
     }
 
     private fun openGroup(shouldClearFragment: Boolean) {
@@ -612,13 +613,13 @@ class HomeActivity :
         fun newIntent(
                 context: Context,
                 clearNotification: Boolean = false,
-                accountCreation: Boolean = false,
+                authenticationDescription: AuthenticationDescription? = null,
                 existingSession: Boolean = false,
                 inviteNotificationRoomId: String? = null
         ): Intent {
             val args = HomeActivityArgs(
                     clearNotification = clearNotification,
-                    accountCreation = accountCreation,
+                    authenticationDescription = authenticationDescription,
                     hasExistingSession = existingSession,
                     inviteNotificationRoomId = inviteNotificationRoomId
             )
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt
index 5f89c89bc9..95201b0aaa 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewActions.kt
@@ -17,8 +17,9 @@
 package im.vector.app.features.home
 
 import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.features.onboarding.AuthenticationDescription
 
 sealed interface HomeActivityViewActions : VectorViewModelAction {
-    object ViewStarted : HomeActivityViewActions
+    data class ViewStarted(val recentAuthentication: AuthenticationDescription?) : HomeActivityViewActions
     object PushPromptHasBeenReviewed : HomeActivityViewActions
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index 9fe8a1f60e..c7265b7c26 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -28,8 +28,12 @@ import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.analytics.AnalyticsTracker
+import im.vector.app.features.analytics.extensions.toAnalyticsType
+import im.vector.app.features.analytics.plan.Signup
 import im.vector.app.features.analytics.store.AnalyticsStore
 import im.vector.app.features.login.ReAuthHelper
+import im.vector.app.features.onboarding.AuthenticationDescription
 import im.vector.app.features.raw.wellknown.ElementWellKnown
 import im.vector.app.features.raw.wellknown.getElementWellknown
 import im.vector.app.features.raw.wellknown.isSecureBackupRequired
@@ -37,8 +41,11 @@ import im.vector.app.features.session.coroutineScope
 import im.vector.app.features.settings.VectorPreferences
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.takeWhile
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@@ -72,7 +79,8 @@ class HomeActivityViewModel @AssistedInject constructor(
         private val reAuthHelper: ReAuthHelper,
         private val analyticsStore: AnalyticsStore,
         private val lightweightSettingsStorage: LightweightSettingsStorage,
-        private val vectorPreferences: VectorPreferences
+        private val vectorPreferences: VectorPreferences,
+        private val analyticsTracker: AnalyticsTracker
 ) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
 
     @AssistedFactory
@@ -84,7 +92,7 @@ class HomeActivityViewModel @AssistedInject constructor(
         override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
             val activity: HomeActivity = viewModelContext.activity()
             val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG)
-            return args?.let { HomeActivityViewState(accountCreation = it.accountCreation) }
+            return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) }
                     ?: super.initialState(viewModelContext)
         }
     }
@@ -93,18 +101,18 @@ class HomeActivityViewModel @AssistedInject constructor(
     private var hasCheckedBootstrap = false
     private var onceTrusted = false
 
-    private fun initialize() {
+    private fun initialize(recentAuthentication: AuthenticationDescription?) {
         if (isInitialized) return
         isInitialized = true
         cleanupFiles()
         observeInitialSync()
         checkSessionPushIsOn()
         observeCrossSigningReset()
-        observeAnalytics()
+        observeAnalytics(recentAuthentication)
         initThreadsMigration()
     }
 
-    private fun observeAnalytics() {
+    private fun observeAnalytics(recentAuthentication: AuthenticationDescription?) {
         if (analyticsConfig.isEnabled) {
             analyticsStore.didAskUserConsentFlow
                     .onEach { didAskUser ->
@@ -113,9 +121,31 @@ class HomeActivityViewModel @AssistedInject constructor(
                         }
                     }
                     .launchIn(viewModelScope)
+
+            recentAuthentication?.let {
+                when (recentAuthentication) {
+                    is AuthenticationDescription.Register -> {
+                        viewModelScope.launch {
+                            analyticsStore.onUserGaveConsent {
+                                analyticsTracker.capture(Signup(authenticationType = recentAuthentication.type.toAnalyticsType()))
+                            }
+                        }
+                    }
+                    AuthenticationDescription.Login       -> {
+                        // do nothing
+                    }
+                }
+            }
         }
     }
 
+    private suspend fun AnalyticsStore.onUserGaveConsent(action: () -> Unit) {
+        userConsentFlow
+                .takeWhile { !it }
+                .onCompletion { action() }
+                .collect()
+    }
+
     private fun cleanupFiles() {
         // Mitigation: delete all cached decrypted files each time the application is started.
         activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache()
@@ -285,7 +315,7 @@ class HomeActivityViewModel @AssistedInject constructor(
             val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
 
             // In case of account creation, it is already done before
-            if (initialState.accountCreation) {
+            if (initialState.authenticationDescription is AuthenticationDescription.Register) {
                 if (isSecureBackupRequired) {
                     _viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
                 } else {
@@ -395,8 +425,8 @@ class HomeActivityViewModel @AssistedInject constructor(
             HomeActivityViewActions.PushPromptHasBeenReviewed -> {
                 vectorPreferences.setDidAskUserToEnableSessionPush()
             }
-            HomeActivityViewActions.ViewStarted               -> {
-                initialize()
+            is HomeActivityViewActions.ViewStarted            -> {
+                initialize(action.recentAuthentication)
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt
index 45fe04fc61..95ab75549f 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt
@@ -17,9 +17,10 @@
 package im.vector.app.features.home
 
 import com.airbnb.mvrx.MavericksState
+import im.vector.app.features.onboarding.AuthenticationDescription
 import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 
 data class HomeActivityViewState(
         val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle,
-        val accountCreation: Boolean = false
+        val authenticationDescription: AuthenticationDescription? = null
 ) : MavericksState
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index 42a9b18558..88cd6cd8d6 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -218,10 +218,7 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
                 // change the screen name
                 analyticsScreenName = MobileScreen.ScreenName.Register
             }
-            val intent = HomeActivity.newIntent(
-                    this,
-                    accountCreation = loginViewState.signMode == SignMode.SignUp
-            )
+            val intent = HomeActivity.newIntent(this)
             startActivity(intent)
             finish()
             return
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt b/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt
index 3cce34b479..1e57a02a6f 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt
@@ -16,12 +16,17 @@
 
 package im.vector.app.features.onboarding
 
+import android.os.Parcelable
 import im.vector.app.features.onboarding.AuthenticationDescription.AuthenticationType
+import kotlinx.parcelize.Parcelize
 import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
 
-sealed interface AuthenticationDescription {
+sealed interface AuthenticationDescription : Parcelable {
+    @Parcelize
     object Login : AuthenticationDescription
-    data class AccountCreated(val type: AuthenticationType) : AuthenticationDescription
+
+    @Parcelize
+    data class Register(val type: AuthenticationType) : AuthenticationDescription
 
     enum class AuthenticationType {
         Password,
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
index e6b5cfc95c..ed8112f369 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
@@ -276,7 +276,7 @@ class Login2Variant(
             is LoginViewEvents2.OnLoginModeNotSupported                    ->
                 onLoginModeNotSupported(event.supportedTypes)
             is LoginViewEvents2.OnSessionCreated                           -> handleOnSessionCreated(event)
-            is LoginViewEvents2.Finish                                     -> terminate(true)
+            is LoginViewEvents2.Finish                                     -> terminate()
             is LoginViewEvents2.CancelRegistration                         -> handleCancelRegistration()
         }
     }
@@ -296,14 +296,13 @@ class Login2Variant(
                     option = commonOption
             )
         } else {
-            terminate(false)
+            terminate()
         }
     }
 
-    private fun terminate(newAccount: Boolean) {
+    private fun terminate() {
         val intent = HomeActivity.newIntent(
-                activity,
-                accountCreation = newAccount
+                activity
         )
         activity.startActivity(intent)
         activity.finish()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index 344a388d7a..460cf70845 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -292,9 +292,8 @@ class OnboardingViewModel @AssistedInject constructor(
                                 else                   -> when (it) {
                                     is RegistrationResult.Complete         -> onSessionCreated(
                                             it.session,
-                                            authenticationDescription = AuthenticationDescription.AccountCreated(
-                                                    awaitState().selectedAuthenticationState.type ?: AuthenticationDescription.AuthenticationType.Other
-                                            )
+                                            authenticationDescription = awaitState().selectedAuthenticationState.description
+                                                    ?: AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Other)
                                     )
                                     is RegistrationResult.NextStep         -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
                                     is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
@@ -325,7 +324,10 @@ class OnboardingViewModel @AssistedInject constructor(
     private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
 
     private fun handleRegisterWith(action: AuthenticateAction.Register) {
-        setState { copy(selectedAuthenticationState = SelectedAuthenticationState(AuthenticationDescription.AuthenticationType.Password)) }
+        setState {
+            val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
+            copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
+        }
         reAuthHelper.data = action.password
         handleRegisterAction(
                 RegisterAction.CreateAccount(
@@ -572,14 +574,14 @@ class OnboardingViewModel @AssistedInject constructor(
         session.configureAndStart(applicationContext)
 
         when (authenticationDescription) {
-            is AuthenticationDescription.AccountCreated -> {
+            is AuthenticationDescription.Register -> {
                 val personalizationState = createPersonalizationState(session, state)
                 setState {
                     copy(isLoading = false, personalizationState = personalizationState)
                 }
                 _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
             }
-            AuthenticationDescription.Login             -> {
+            AuthenticationDescription.Login       -> {
                 setState { copy(isLoading = false) }
                 _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
             }
@@ -753,7 +755,10 @@ class OnboardingViewModel @AssistedInject constructor(
     }
 
     fun getSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? {
-        setState { copy(selectedAuthenticationState = SelectedAuthenticationState(provider.toAuthenticationType())) }
+        setState {
+            val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType())
+            copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
+        }
         return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id)
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
index 9b9bf9bf4f..e91fee4d21 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
@@ -86,5 +86,5 @@ data class PersonalizationState(
 
 @Parcelize
 data class SelectedAuthenticationState(
-        val type: AuthenticationDescription.AuthenticationType? = null,
+        val description: AuthenticationDescription? = null,
 ) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
index 7324c4fbb1..a97d178084 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
@@ -134,7 +134,7 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
             viewModel.getSsoUrl(
                     redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
                     deviceId = deviceId,
-                    providerId = id
+                    provider = id
             )?.let { openInCustomTab(it) }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
index 7a3729ac69..355200ca30 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
@@ -216,7 +216,7 @@ class FtueAuthVariant(
             is OnboardingViewEvents.OnAccountCreated                           -> onAccountCreated()
             OnboardingViewEvents.OnAccountSignedIn                             -> onAccountSignedIn()
             OnboardingViewEvents.OnChooseDisplayName                           -> onChooseDisplayName()
-            OnboardingViewEvents.OnTakeMeHome                                  -> navigateToHome(createdAccount = true)
+            OnboardingViewEvents.OnTakeMeHome                                  -> navigateToHome()
             OnboardingViewEvents.OnChooseProfilePicture                        -> onChooseProfilePicture()
             OnboardingViewEvents.OnPersonalizationComplete                     -> onPersonalizationComplete()
             OnboardingViewEvents.OnBack                                        -> activity.popBackstack()
@@ -467,7 +467,7 @@ class FtueAuthVariant(
     }
 
     private fun onAccountSignedIn() {
-        navigateToHome(createdAccount = false)
+        navigateToHome()
     }
 
     private fun onAccountCreated() {
@@ -479,10 +479,12 @@ class FtueAuthVariant(
         )
     }
 
-    private fun navigateToHome(createdAccount: Boolean) {
-        val intent = HomeActivity.newIntent(activity, accountCreation = createdAccount)
-        activity.startActivity(intent)
-        activity.finish()
+    private fun navigateToHome() {
+        withState(onboardingViewModel) {
+            val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description)
+            activity.startActivity(intent)
+            activity.finish()
+        }
     }
 
     private fun onChooseDisplayName() {