1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.communal.domain.interactor 18 19 import android.provider.Settings 20 import com.android.systemui.communal.data.repository.CommunalTutorialRepository 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Application 23 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 24 import com.android.systemui.log.dagger.CommunalTableLog 25 import com.android.systemui.log.table.TableLogBuffer 26 import com.android.systemui.log.table.logDiffsForTable 27 import javax.inject.Inject 28 import kotlinx.coroutines.CoroutineScope 29 import kotlinx.coroutines.flow.Flow 30 import kotlinx.coroutines.flow.SharingStarted 31 import kotlinx.coroutines.flow.StateFlow 32 import kotlinx.coroutines.flow.combine 33 import kotlinx.coroutines.flow.distinctUntilChanged 34 import kotlinx.coroutines.flow.emptyFlow 35 import kotlinx.coroutines.flow.filterNotNull 36 import kotlinx.coroutines.flow.flatMapLatest 37 import kotlinx.coroutines.flow.flowOf 38 import kotlinx.coroutines.flow.map 39 import kotlinx.coroutines.flow.stateIn 40 import kotlinx.coroutines.flow.transformWhile 41 import com.android.app.tracing.coroutines.launchTraced as launch 42 43 /** Encapsulates business-logic related to communal tutorial state. */ 44 @SysUISingleton 45 class CommunalTutorialInteractor 46 @Inject 47 constructor( 48 @Application private val scope: CoroutineScope, 49 private val communalTutorialRepository: CommunalTutorialRepository, 50 keyguardInteractor: KeyguardInteractor, 51 private val communalSettingsInteractor: CommunalSettingsInteractor, 52 communalInteractor: CommunalInteractor, 53 @CommunalTableLog tableLogBuffer: TableLogBuffer, 54 ) { 55 /** An observable for whether the tutorial is available. */ 56 val isTutorialAvailable: StateFlow<Boolean> = 57 combine( 58 communalInteractor.isCommunalAvailable, 59 keyguardInteractor.isKeyguardVisible, 60 communalTutorialRepository.tutorialSettingState, 61 ) { isCommunalAvailable, isKeyguardVisible, tutorialSettingState -> 62 isCommunalAvailable && 63 isKeyguardVisible && 64 tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED 65 } 66 .logDiffsForTable( 67 tableLogBuffer = tableLogBuffer, 68 columnName = "isTutorialAvailable", 69 initialValue = false, 70 ) 71 .stateIn( 72 scope = scope, 73 started = SharingStarted.WhileSubscribed(), 74 initialValue = false, 75 ) 76 77 /** 78 * A flow of the new tutorial state after transitioning. The new state will be calculated based 79 * on the current tutorial state and transition state as following: 80 * HUB_MODE_TUTORIAL_NOT_STARTED + communal scene -> HUB_MODE_TUTORIAL_STARTED 81 * HUB_MODE_TUTORIAL_STARTED + non-communal scene -> HUB_MODE_TUTORIAL_COMPLETED 82 * HUB_MODE_TUTORIAL_COMPLETED + any scene -> won't emit 83 */ 84 private val tutorialStateToUpdate: Flow<Int> = 85 communalTutorialRepository.tutorialSettingState 86 .flatMapLatest { tutorialSettingState -> 87 if (tutorialSettingState == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) { 88 return@flatMapLatest flowOf(null) 89 } 90 communalInteractor.isCommunalShowing.map { isCommunalShowing -> 91 nextStateAfterTransition( 92 tutorialSettingState, 93 isCommunalShowing, 94 ) 95 } 96 } 97 .filterNotNull() 98 .distinctUntilChanged() 99 100 private fun nextStateAfterTransition(tutorialState: Int, isCommunalShowing: Boolean): Int? { 101 if (tutorialState == Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED && isCommunalShowing) { 102 return Settings.Secure.HUB_MODE_TUTORIAL_STARTED 103 } 104 if (tutorialState == Settings.Secure.HUB_MODE_TUTORIAL_STARTED && !isCommunalShowing) { 105 return Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED 106 } 107 return null 108 } 109 110 private fun listenForTransitionToUpdateTutorialState() { 111 scope.launch { 112 communalSettingsInteractor.isCommunalEnabled 113 .flatMapLatest { enabled -> 114 if (!enabled) { 115 emptyFlow() 116 } else { 117 tutorialStateToUpdate 118 } 119 } 120 .transformWhile { tutorialState -> 121 emit(tutorialState) 122 tutorialState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED 123 } 124 .collect { tutorialState -> 125 communalTutorialRepository.setTutorialState(tutorialState) 126 } 127 } 128 } 129 130 init { 131 listenForTransitionToUpdateTutorialState() 132 } 133 } 134