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.ExperimentalCoroutinesApi 30 import kotlinx.coroutines.flow.Flow 31 import kotlinx.coroutines.flow.SharingStarted 32 import kotlinx.coroutines.flow.StateFlow 33 import kotlinx.coroutines.flow.combine 34 import kotlinx.coroutines.flow.distinctUntilChanged 35 import kotlinx.coroutines.flow.emptyFlow 36 import kotlinx.coroutines.flow.filterNotNull 37 import kotlinx.coroutines.flow.flatMapLatest 38 import kotlinx.coroutines.flow.flowOf 39 import kotlinx.coroutines.flow.map 40 import kotlinx.coroutines.flow.stateIn 41 import kotlinx.coroutines.flow.transformWhile 42 import kotlinx.coroutines.launch 43 44 /** Encapsulates business-logic related to communal tutorial state. */ 45 @OptIn(ExperimentalCoroutinesApi::class) 46 @SysUISingleton 47 class CommunalTutorialInteractor 48 @Inject 49 constructor( 50 @Application private val scope: CoroutineScope, 51 private val communalTutorialRepository: CommunalTutorialRepository, 52 keyguardInteractor: KeyguardInteractor, 53 private val communalSettingsInteractor: CommunalSettingsInteractor, 54 communalInteractor: CommunalInteractor, 55 @CommunalTableLog tableLogBuffer: TableLogBuffer, 56 ) { 57 /** An observable for whether the tutorial is available. */ 58 val isTutorialAvailable: StateFlow<Boolean> = 59 combine( 60 communalInteractor.isCommunalAvailable, 61 keyguardInteractor.isKeyguardVisible, 62 communalTutorialRepository.tutorialSettingState, 63 ) { isCommunalAvailable, isKeyguardVisible, tutorialSettingState -> 64 isCommunalAvailable && 65 isKeyguardVisible && 66 tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED 67 } 68 .logDiffsForTable( 69 tableLogBuffer = tableLogBuffer, 70 columnPrefix = "", 71 columnName = "isTutorialAvailable", 72 initialValue = false, 73 ) 74 .stateIn( 75 scope = scope, 76 started = SharingStarted.WhileSubscribed(), 77 initialValue = false, 78 ) 79 80 /** 81 * A flow of the new tutorial state after transitioning. The new state will be calculated based 82 * on the current tutorial state and transition state as following: 83 * HUB_MODE_TUTORIAL_NOT_STARTED + communal scene -> HUB_MODE_TUTORIAL_STARTED 84 * HUB_MODE_TUTORIAL_STARTED + non-communal scene -> HUB_MODE_TUTORIAL_COMPLETED 85 * HUB_MODE_TUTORIAL_COMPLETED + any scene -> won't emit 86 */ 87 private val tutorialStateToUpdate: Flow<Int> = 88 communalTutorialRepository.tutorialSettingState 89 .flatMapLatest { tutorialSettingState -> 90 if (tutorialSettingState == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) { 91 return@flatMapLatest flowOf(null) 92 } 93 communalInteractor.isCommunalShowing.map { isCommunalShowing -> 94 nextStateAfterTransition( 95 tutorialSettingState, 96 isCommunalShowing, 97 ) 98 } 99 } 100 .filterNotNull() 101 .distinctUntilChanged() 102 103 private fun nextStateAfterTransition(tutorialState: Int, isCommunalShowing: Boolean): Int? { 104 if (tutorialState == Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED && isCommunalShowing) { 105 return Settings.Secure.HUB_MODE_TUTORIAL_STARTED 106 } 107 if (tutorialState == Settings.Secure.HUB_MODE_TUTORIAL_STARTED && !isCommunalShowing) { 108 return Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED 109 } 110 return null 111 } 112 113 private fun listenForTransitionToUpdateTutorialState() { 114 scope.launch { 115 communalSettingsInteractor.isCommunalEnabled 116 .flatMapLatest { enabled -> 117 if (!enabled) { 118 emptyFlow() 119 } else { 120 tutorialStateToUpdate 121 } 122 } 123 .transformWhile { tutorialState -> 124 emit(tutorialState) 125 tutorialState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED 126 } 127 .collect { tutorialState -> 128 communalTutorialRepository.setTutorialState(tutorialState) 129 } 130 } 131 } 132 133 init { 134 listenForTransitionToUpdateTutorialState() 135 } 136 } 137