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