Analytics: manage account data

This commit is contained in:
Benoit Marty 2021-11-24 09:51:11 +01:00 committed by Benoit Marty
parent 8752fe1e69
commit a3173d89e5
4 changed files with 162 additions and 0 deletions

View File

@ -20,6 +20,7 @@ import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.IntoMap
import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel
import im.vector.app.features.auth.ReAuthViewModel
import im.vector.app.features.call.VectorCallViewModel
@ -460,6 +461,11 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(AnalyticsConsentViewModel::class)
fun analyticsConsentViewModelFactory(factory: AnalyticsConsentViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(AnalyticsAccountDataViewModel::class)
fun analyticsAccountDataViewModelFactory(factory: AnalyticsAccountDataViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(HomeServerCapabilitiesViewModel::class)

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021 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.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AnalyticsAccountDataContent(
// A randomly generated analytics token for this user.
// This is suggested to be a 128-bit hex encoded string.
@Json(name = "id")
val id: String? = null,
// Boolean indicating whether the user has opted in.
// If null or not set, the user hasn't yet given consent either way
@Json(name = "pseudonymousAnalyticsOptIn")
val pseudonymousAnalyticsOptIn: Boolean? = null,
// Boolean indicating whether to show the analytics opt-in prompt.
@Json(name = "showPseudonymousAnalyticsPrompt")
val showPseudonymousAnalyticsPrompt: Boolean? = null
)

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2021 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.accountdata
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.VectorAnalytics
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.flow.flow
import timber.log.Timber
import java.util.UUID
data class DummyState(
val dummy: Boolean = false
) : MavericksState
class AnalyticsAccountDataViewModel @AssistedInject constructor(
@Assisted initialState: DummyState,
private val session: Session,
private val analytics: VectorAnalytics
) : VectorViewModel<DummyState, EmptyAction, EmptyViewEvents>(initialState) {
private var checkDone: Boolean = false
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<AnalyticsAccountDataViewModel, DummyState> {
override fun create(initialState: DummyState): AnalyticsAccountDataViewModel
}
companion object : MavericksViewModelFactory<AnalyticsAccountDataViewModel, DummyState> by hiltMavericksViewModelFactory() {
private const val ANALYTICS_EVENT_TYPE = "im.vector.analytics";
}
init {
observeAccountData()
observeInitSync()
}
private fun observeInitSync() {
combine(
session.getSyncStatusLive().asFlow(),
analytics.getUserConsent(),
analytics.getAnalyticsId()
) { status, userConsent, analyticsId ->
if (status is SyncStatusService.Status.IncrementalSyncIdle &&
userConsent &&
analyticsId.isEmpty() &&
!checkDone) {
// Initial sync is over, analytics Id from account data is missing and user has given consent to use analytics
checkDone = true
createAnalyticsAccountData()
}
}.launchIn(viewModelScope)
}
private fun observeAccountData() {
session.flow()
.liveUserAccountData(setOf(ANALYTICS_EVENT_TYPE))
.mapNotNull { it.firstOrNull() }
.mapNotNull { it.content.toModel<AnalyticsAccountDataContent>() }
.onEach { analyticsAccountDataContent ->
if (analyticsAccountDataContent.id.isNullOrEmpty()) {
// Probably consent revoked from Element Web
// Ignore here
Timber.d("Consent revoked from Element Web?")
} else {
Timber.d("AnalyticsId has been retrieved")
analytics.setAnalyticsId(analyticsAccountDataContent.id)
}
}
.launchIn(viewModelScope)
}
override fun handle(action: EmptyAction) {
// No op
}
private fun createAnalyticsAccountData() {
val content = AnalyticsAccountDataContent(
id = UUID.randomUUID().toString()
)
viewModelScope.launch {
session.accountDataService().updateUserAccountData(ANALYTICS_EVENT_TYPE, content.toContent())
}
}
}

View File

@ -48,6 +48,7 @@ import im.vector.app.core.pushers.PushersManager
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
import im.vector.app.features.disclaimer.showDisclaimerDialog
import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.navigation.Navigator
@ -103,6 +104,8 @@ class HomeActivity :
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
@Suppress("UNUSED")
private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
private val promoteRestrictedViewModel: PromoteRestrictedViewModel by viewModel()