• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.qs.panels.data.repository
18 
19 import android.content.Context
20 import android.content.IntentFilter
21 import android.content.SharedPreferences
22 import com.android.systemui.backup.BackupHelper
23 import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
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.qs.panels.shared.model.PanelsLog
30 import com.android.systemui.qs.pipeline.shared.TileSpec
31 import com.android.systemui.qs.pipeline.shared.TilesUpgradePath
32 import com.android.systemui.settings.UserFileManager
33 import com.android.systemui.user.data.repository.UserRepository
34 import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
35 import com.android.systemui.util.kotlin.emitOnStart
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineDispatcher
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.combine
40 import kotlinx.coroutines.flow.flatMapLatest
41 import kotlinx.coroutines.flow.flowOn
42 import kotlinx.coroutines.flow.map
43 import kotlinx.coroutines.flow.onEach
44 
45 /** Repository for QS user preferences. */
46 @SysUISingleton
47 class QSPreferencesRepository
48 @Inject
49 constructor(
50     private val userFileManager: UserFileManager,
51     private val userRepository: UserRepository,
52     private val defaultLargeTilesRepository: DefaultLargeTilesRepository,
53     @Background private val backgroundDispatcher: CoroutineDispatcher,
54     @PanelsLog private val logBuffer: LogBuffer,
55     broadcastDispatcher: BroadcastDispatcher,
56 ) {
57     private val logger by lazy { Logger(logBuffer, TAG) }
58 
59     private val backupRestorationEvents: Flow<Unit> =
60         broadcastDispatcher
61             .broadcastFlow(
62                 filter = IntentFilter(ACTION_RESTORE_FINISHED),
63                 flags = Context.RECEIVER_NOT_EXPORTED,
64                 permission = BackupHelper.PERMISSION_SELF,
65             )
66             .onEach { logger.i("Restored state for QS preferences.") }
67             .emitOnStart()
68 
69     /** Set of [TileSpec] to display as large tiles for the current user. */
70     val largeTilesSpecs: Flow<Set<TileSpec>> =
71         combine(backupRestorationEvents, userRepository.selectedUserInfo, ::Pair)
72             .flatMapLatest { (_, userInfo) ->
73                 val prefs = getSharedPrefs(userInfo.id)
74                 prefs.observe().emitOnStart().map {
75                     prefs
76                         .getStringSet(
77                             LARGE_TILES_SPECS_KEY,
78                             defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet(),
79                         )
80                         ?.map { TileSpec.create(it) }
81                         ?.toSet() ?: defaultLargeTilesRepository.defaultLargeTiles
82                 }
83             }
84             .flowOn(backgroundDispatcher)
85 
86     /** Sets for the current user the set of [TileSpec] to display as large tiles. */
87     fun writeLargeTileSpecs(specs: Set<TileSpec>) {
88         with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
89             writeLargeTileSpecs(specs)
90             setLargeTilesDefault(false)
91         }
92     }
93 
94     suspend fun deleteLargeTileDataJob() {
95         userRepository.selectedUserInfo.collect { userInfo ->
96             getSharedPrefs(userInfo.id)
97                 .edit()
98                 .remove(LARGE_TILES_SPECS_KEY)
99                 .remove(LARGE_TILES_DEFAULT_KEY)
100                 .apply()
101         }
102     }
103 
104     private fun SharedPreferences.writeLargeTileSpecs(specs: Set<TileSpec>) {
105         edit().putStringSet(LARGE_TILES_SPECS_KEY, specs.map { it.spec }.toSet()).apply()
106     }
107 
108     /**
109      * Sets the initial set of large tiles. One of the following cases will happen:
110      * * If we are setting the default set (no value stored in settings for the list of tiles), set
111      *   the large tiles based on [defaultLargeTilesRepository]. We do this to signal future reboots
112      *   that we have performed the upgrade path once. In this case, we will mark that we set them
113      *   as the default in case a restore needs to modify them later.
114      * * If we got a list of tiles restored from a device and nothing has modified the list of
115      *   tiles, set all the restored tiles to large. Note that if we also restored a set of large
116      *   tiles before this was called, [LARGE_TILES_DEFAULT_KEY] will be false and we won't
117      *   overwrite it.
118      * * If we got a list of tiles from settings, we consider that we upgraded in place and then we
119      *   will set all those tiles to large IF there's no current set of large tiles.
120      *
121      * Even if largeTilesSpec is read Eagerly before we know if we are in an initial state, because
122      * we are not writing the default values to the SharedPreferences, the file will not contain the
123      * key and this call will succeed, as long as there hasn't been any calls to setLargeTilesSpecs
124      * for that user before.
125      */
126     fun setInitialOrUpgradeLargeTiles(upgradePath: TilesUpgradePath, userId: Int) {
127         with(getSharedPrefs(userId)) {
128             when (upgradePath) {
129                 is TilesUpgradePath.DefaultSet -> {
130                     writeLargeTileSpecs(defaultLargeTilesRepository.defaultLargeTiles)
131                     logger.i("Large tiles set to default on init")
132                     setLargeTilesDefault(true)
133                 }
134                 is TilesUpgradePath.RestoreFromBackup -> {
135                     if (
136                         getBoolean(LARGE_TILES_DEFAULT_KEY, false) ||
137                             !contains(LARGE_TILES_SPECS_KEY)
138                     ) {
139                         writeLargeTileSpecs(upgradePath.value)
140                         logger.i("Tiles restored from backup set to large: ${upgradePath.value}")
141                         setLargeTilesDefault(false)
142                     }
143                 }
144                 is TilesUpgradePath.ReadFromSettings -> {
145                     if (!contains(LARGE_TILES_SPECS_KEY)) {
146                         writeLargeTileSpecs(upgradePath.value)
147                         logger.i("Tiles read from settings set to large: ${upgradePath.value}")
148                         setLargeTilesDefault(false)
149                     }
150                 }
151             }
152         }
153     }
154 
155     private fun SharedPreferences.setLargeTilesDefault(value: Boolean) {
156         edit().putBoolean(LARGE_TILES_DEFAULT_KEY, value).apply()
157     }
158 
159     private fun getSharedPrefs(userId: Int): SharedPreferences {
160         return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
161     }
162 
163     companion object {
164         private const val TAG = "QSPreferencesRepository"
165         private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
166         private const val LARGE_TILES_DEFAULT_KEY = "large_tiles_default"
167         const val FILE_NAME = "quick_settings_prefs"
168     }
169 }
170