• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.data.repository
18 
19 import android.content.Context
20 import android.content.IntentFilter
21 import android.content.SharedPreferences
22 import android.content.pm.UserInfo
23 import com.android.systemui.backup.BackupHelper
24 import com.android.systemui.broadcast.BroadcastDispatcher
25 import com.android.systemui.dagger.SysUISingleton
26 import com.android.systemui.dagger.qualifiers.Background
27 import com.android.systemui.log.LogBuffer
28 import com.android.systemui.log.core.Logger
29 import com.android.systemui.log.dagger.CommunalLog
30 import com.android.systemui.settings.UserFileManager
31 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
32 import com.android.systemui.util.kotlin.emitOnStart
33 import javax.inject.Inject
34 import kotlinx.coroutines.CoroutineDispatcher
35 import kotlinx.coroutines.flow.Flow
36 import kotlinx.coroutines.flow.flatMapLatest
37 import kotlinx.coroutines.flow.flowOn
38 import kotlinx.coroutines.flow.map
39 import kotlinx.coroutines.flow.onEach
40 import kotlinx.coroutines.withContext
41 
42 /**
43  * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA
44  * tile been dismissed?"
45  */
46 interface CommunalPrefsRepository {
47 
48     /** Whether the CTA tile has been dismissed. */
isCtaDismissednull49     fun isCtaDismissed(user: UserInfo): Flow<Boolean>
50 
51     /** Save the CTA tile dismissed state for the current user. */
52     suspend fun setCtaDismissed(user: UserInfo)
53 
54     /** Whether hub onboarding has been dismissed. */
55     fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean>
56 
57     /** Save the hub onboarding dismissed state for the current user. */
58     suspend fun setHubOnboardingDismissed(user: UserInfo)
59 
60     /** Whether dream button tooltip has been dismissed. */
61     fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean>
62 
63     /** Save the dream button tooltip dismissed state for the current user. */
64     suspend fun setDreamButtonTooltipDismissed(user: UserInfo)
65 }
66 
67 @SysUISingleton
68 class CommunalPrefsRepositoryImpl
69 @Inject
70 constructor(
71     @Background private val bgDispatcher: CoroutineDispatcher,
72     private val userFileManager: UserFileManager,
73     broadcastDispatcher: BroadcastDispatcher,
74     @CommunalLog logBuffer: LogBuffer,
75 ) : CommunalPrefsRepository {
76     private val logger by lazy { Logger(logBuffer, TAG) }
77 
78     /**
79      * Emits an event each time a Backup & Restore restoration job is completed, and once at the
80      * start of collection.
81      */
82     private val backupRestorationEvents: Flow<Unit> =
83         broadcastDispatcher
84             .broadcastFlow(
85                 filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
86                 flags = Context.RECEIVER_NOT_EXPORTED,
87                 permission = BackupHelper.PERMISSION_SELF,
88             )
89             .onEach { logger.i("Restored state for communal preferences.") }
90             .emitOnStart()
91 
92     override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
93         readKeyForUser(user, CTA_DISMISSED_STATE)
94 
95     override suspend fun setCtaDismissed(user: UserInfo) =
96         setBooleanKeyValueForUser(user, CTA_DISMISSED_STATE, "Dismissed CTA tile")
97 
98     override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> =
99         readKeyForUser(user, HUB_ONBOARDING_DISMISSED_STATE)
100 
101     override suspend fun setHubOnboardingDismissed(user: UserInfo) =
102         setBooleanKeyValueForUser(user, HUB_ONBOARDING_DISMISSED_STATE, "Dismissed hub onboarding")
103 
104     override fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean> =
105         readKeyForUser(user, DREAM_BUTTON_TOOLTIP_DISMISSED_STATE)
106 
107     override suspend fun setDreamButtonTooltipDismissed(user: UserInfo) =
108         setBooleanKeyValueForUser(
109             user,
110             DREAM_BUTTON_TOOLTIP_DISMISSED_STATE,
111             "Dismissed dream button tooltip",
112         )
113 
114     private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
115         return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
116     }
117 
118     private suspend fun setBooleanKeyValueForUser(user: UserInfo, key: String, logMsg: String) =
119         withContext(bgDispatcher) {
120             getSharedPrefsForUser(user).edit().putBoolean(key, true).apply()
121             logger.i(logMsg)
122         }
123 
124     private fun readKeyForUser(user: UserInfo, key: String): Flow<Boolean> {
125         return backupRestorationEvents
126             .flatMapLatest {
127                 val sharedPrefs = getSharedPrefsForUser(user)
128                 sharedPrefs.observe().emitOnStart().map { sharedPrefs.getBoolean(key, false) }
129             }
130             .flowOn(bgDispatcher)
131     }
132 
133     companion object {
134         const val TAG = "CommunalPrefsRepository"
135         const val FILE_NAME = "communal_hub_prefs"
136         const val CTA_DISMISSED_STATE = "cta_dismissed"
137         const val HUB_ONBOARDING_DISMISSED_STATE = "hub_onboarding_dismissed"
138         const val DREAM_BUTTON_TOOLTIP_DISMISSED_STATE = "dream_button_tooltip_dismissed_state"
139     }
140 }
141