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 package com.android.systemui.communal.data.repository
17 
18 import android.provider.Settings
19 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
20 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
21 import android.provider.Settings.Secure.HubModeTutorialState
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.dagger.qualifiers.Background
25 import com.android.systemui.log.LogBuffer
26 import com.android.systemui.log.core.Logger
27 import com.android.systemui.log.dagger.CommunalLog
28 import com.android.systemui.log.dagger.CommunalTableLog
29 import com.android.systemui.log.table.TableLogBuffer
30 import com.android.systemui.log.table.logDiffsForTable
31 import com.android.systemui.user.data.repository.UserRepository
32 import com.android.systemui.util.settings.SecureSettings
33 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
34 import javax.inject.Inject
35 import kotlinx.coroutines.CoroutineDispatcher
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.ExperimentalCoroutinesApi
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.StateFlow
41 import kotlinx.coroutines.flow.emptyFlow
42 import kotlinx.coroutines.flow.filterNotNull
43 import kotlinx.coroutines.flow.flatMapLatest
44 import kotlinx.coroutines.flow.map
45 import kotlinx.coroutines.flow.onStart
46 import kotlinx.coroutines.flow.shareIn
47 import kotlinx.coroutines.flow.stateIn
48 import kotlinx.coroutines.withContext
49 
50 /**
51  * Repository for the current state of hub mode tutorial. Valid states are defined in
52  * [HubModeTutorialState].
53  */
54 interface CommunalTutorialRepository {
55     /** Emits the tutorial state stored in Settings */
56     val tutorialSettingState: StateFlow<Int>
57 
58     /** Update the tutorial state */
59     suspend fun setTutorialState(@HubModeTutorialState state: Int)
60 }
61 
62 @OptIn(ExperimentalCoroutinesApi::class)
63 @SysUISingleton
64 class CommunalTutorialRepositoryImpl
65 @Inject
66 constructor(
67     @Application private val applicationScope: CoroutineScope,
68     @Background private val backgroundDispatcher: CoroutineDispatcher,
69     private val userRepository: UserRepository,
70     private val secureSettings: SecureSettings,
71     @CommunalLog logBuffer: LogBuffer,
72     @CommunalTableLog tableLogBuffer: TableLogBuffer,
73 ) : CommunalTutorialRepository {
74 
75     companion object {
76         private const val TAG = "CommunalTutorialRepository"
77 
78         const val MIN_TUTORIAL_VERSION = HUB_MODE_TUTORIAL_COMPLETED
79 
80         // A version number which ensures that users, regardless of their completion of previous
81         // versions, see the updated tutorial when this number is bumped.
82         const val CURRENT_TUTORIAL_VERSION = MIN_TUTORIAL_VERSION + 1
83     }
84 
85     private data class SettingsState(
86         @HubModeTutorialState val hubModeTutorialState: Int? = null,
87     )
88 
89     private val logger = Logger(logBuffer, TAG)
90 
91     private val settingsState: Flow<SettingsState> =
92         userRepository.selectedUserInfo
userInfonull93             .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
94             .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
95 
96     /** Emits the state of tutorial state in settings */
97     override val tutorialSettingState: StateFlow<Int> =
98         settingsState
<lambda>null99             .map { it.hubModeTutorialState }
100             .filterNotNull()
101             .logDiffsForTable(
102                 tableLogBuffer = tableLogBuffer,
103                 columnPrefix = "",
104                 columnName = "tutorialSettingState",
105                 initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
106             )
107             .stateIn(
108                 scope = applicationScope,
109                 started = SharingStarted.WhileSubscribed(),
110                 initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
111             )
112 
observeSettingsnull113     private fun observeSettings(userId: Int): Flow<SettingsState> =
114         secureSettings
115             .observerFlow(
116                 userId = userId,
117                 names = arrayOf(Settings.Secure.HUB_MODE_TUTORIAL_STATE),
118             )
119             // Force an update
120             .onStart { emit(Unit) }
<lambda>null121             .map { readFromSettings(userId) }
122 
readFromSettingsnull123     private suspend fun readFromSettings(userId: Int): SettingsState =
124         withContext(backgroundDispatcher) {
125             var hubModeTutorialState =
126                 secureSettings.getIntForUser(
127                     Settings.Secure.HUB_MODE_TUTORIAL_STATE,
128                     HUB_MODE_TUTORIAL_NOT_STARTED,
129                     userId,
130                 )
131 
132             if (hubModeTutorialState >= CURRENT_TUTORIAL_VERSION) {
133                 // Tutorial is considered "completed" if the user has completed the current or a
134                 // newer version.
135                 hubModeTutorialState = HUB_MODE_TUTORIAL_COMPLETED
136             } else if (hubModeTutorialState >= MIN_TUTORIAL_VERSION) {
137                 // Tutorial is considered "not started" if the user completed a version older than
138                 // the current.
139                 hubModeTutorialState = HUB_MODE_TUTORIAL_NOT_STARTED
140             }
141             val settingsState = SettingsState(hubModeTutorialState)
142             logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
143                 int1 = userId
144                 str1 = settingsState.hubModeTutorialState.toString()
145             }
146 
147             settingsState
148         }
149 
setTutorialStatenull150     override suspend fun setTutorialState(state: Int): Unit =
151         withContext(backgroundDispatcher) {
152             val userId = userRepository.getSelectedUserInfo().id
153             if (tutorialSettingState.value == state) {
154                 return@withContext
155             }
156             val newState =
157                 if (state == HUB_MODE_TUTORIAL_COMPLETED) CURRENT_TUTORIAL_VERSION else state
158             logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
159                 int1 = newState
160                 int2 = userId
161             }
162             secureSettings.putIntForUser(
163                 Settings.Secure.HUB_MODE_TUTORIAL_STATE,
164                 newState,
165                 userId,
166             )
167         }
168 }
169 
170 // TODO(b/320769333): delete me and use the real repo above when tutorial is ready.
171 @SysUISingleton
172 class CommunalTutorialDisabledRepositoryImpl
173 @Inject
174 constructor(
175     @Application private val applicationScope: CoroutineScope,
176 ) : CommunalTutorialRepository {
177     override val tutorialSettingState: StateFlow<Int> =
178         emptyFlow<Int>()
179             .stateIn(
180                 scope = applicationScope,
181                 started = SharingStarted.WhileSubscribed(),
182                 initialValue = HUB_MODE_TUTORIAL_COMPLETED,
183             )
184 
setTutorialStatenull185     override suspend fun setTutorialState(state: Int) {
186         // Do nothing
187     }
188 }
189