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