From 28050488baf72bfb7f75a746e859c8ed048daaf5 Mon Sep 17 00:00:00 2001 From: Adam Brown 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(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(), 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() {