1 /* <lambda>null2 * Copyright (C) 2021 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 18 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.database.ContentObserver 24 import android.net.Uri 25 import android.os.Handler 26 import android.os.UserHandle 27 import android.provider.Settings 28 import android.text.TextUtils 29 import android.util.ArraySet 30 import android.util.Log 31 import androidx.annotation.GuardedBy 32 import androidx.annotation.VisibleForTesting 33 import com.android.systemui.Dumpable 34 import com.android.systemui.broadcast.BroadcastDispatcher 35 import com.android.systemui.dagger.SysUISingleton 36 import com.android.systemui.dagger.qualifiers.Background 37 import com.android.systemui.dagger.qualifiers.Main 38 import com.android.systemui.dump.DumpManager 39 import com.android.systemui.util.UserAwareController 40 import com.android.systemui.util.settings.SecureSettings 41 import java.io.PrintWriter 42 import java.util.concurrent.Executor 43 import javax.inject.Inject 44 45 private const val TAG = "AutoAddTracker" 46 private const val DELIMITER = "," 47 48 /** 49 * Class to track tiles that have been auto-added 50 * 51 * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES]. 52 * 53 * It also handles restore gracefully. 54 */ 55 class AutoAddTracker 56 @VisibleForTesting 57 constructor( 58 private val secureSettings: SecureSettings, 59 private val broadcastDispatcher: BroadcastDispatcher, 60 private val qsHost: QSHost, 61 private val dumpManager: DumpManager, 62 private val mainHandler: Handler?, 63 private val backgroundExecutor: Executor, 64 private var userId: Int 65 ) : UserAwareController, Dumpable { 66 67 companion object { 68 private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) 69 } 70 71 @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>() 72 private var restoredTiles: Map<String, AutoTile>? = null 73 74 override val currentUserId: Int 75 get() = userId 76 77 private val contentObserver = 78 object : ContentObserver(mainHandler) { 79 override fun onChange( 80 selfChange: Boolean, 81 uris: Collection<Uri>, 82 flags: Int, 83 _userId: Int 84 ) { 85 if (_userId != userId) { 86 // Ignore changes outside of our user. We'll load the correct value on user 87 // change 88 return 89 } 90 loadTiles() 91 } 92 } 93 94 private val restoreReceiver = 95 object : BroadcastReceiver() { 96 override fun onReceive(context: Context, intent: Intent) { 97 if (intent.action != Intent.ACTION_SETTING_RESTORED) return 98 processRestoreIntent(intent) 99 } 100 } 101 102 private fun processRestoreIntent(intent: Intent) { 103 when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) { 104 Settings.Secure.QS_TILES -> { 105 restoredTiles = 106 intent 107 .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) 108 ?.split(DELIMITER) 109 ?.mapIndexed(::AutoTile) 110 ?.associateBy(AutoTile::tileType) 111 ?: run { 112 Log.w(TAG, "Null restored tiles for user $userId") 113 emptyMap() 114 } 115 } 116 Settings.Secure.QS_AUTO_ADDED_TILES -> { 117 restoredTiles?.let { restoredTiles -> 118 val restoredAutoAdded = 119 intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER) 120 ?: emptyList() 121 val autoAddedBeforeRestore = 122 intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER) 123 ?: emptyList() 124 125 val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles } 126 if (tilesToRemove.isNotEmpty()) { 127 Log.d(TAG, "Removing tiles: $tilesToRemove") 128 qsHost.removeTiles(tilesToRemove) 129 } 130 val tiles = 131 synchronized(autoAdded) { 132 autoAdded.clear() 133 autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) 134 getTilesFromListLocked() 135 } 136 saveTiles(tiles) 137 } 138 ?: run { 139 Log.w( 140 TAG, 141 "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + 142 "${Settings.Secure.QS_TILES} for user $userId" 143 ) 144 } 145 } 146 else -> {} // Do nothing for other Settings 147 } 148 } 149 150 /** Init method must be called after construction to start listening */ 151 fun initialize() { 152 dumpManager.registerDumpable(TAG, this) 153 loadTiles() 154 secureSettings.registerContentObserverForUserSync( 155 secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), 156 contentObserver, 157 UserHandle.USER_ALL 158 ) 159 registerBroadcastReceiver() 160 } 161 162 /** Unregister listeners, receivers and observers */ 163 fun destroy() { 164 dumpManager.unregisterDumpable(TAG) 165 secureSettings.unregisterContentObserverSync(contentObserver) 166 unregisterBroadcastReceiver() 167 } 168 169 private fun registerBroadcastReceiver() { 170 broadcastDispatcher.registerReceiver( 171 restoreReceiver, 172 FILTER, 173 backgroundExecutor, 174 UserHandle.of(userId) 175 ) 176 } 177 178 private fun unregisterBroadcastReceiver() { 179 broadcastDispatcher.unregisterReceiver(restoreReceiver) 180 } 181 182 override fun changeUser(newUser: UserHandle) { 183 if (newUser.identifier == userId) return 184 unregisterBroadcastReceiver() 185 userId = newUser.identifier 186 restoredTiles = null 187 loadTiles() 188 registerBroadcastReceiver() 189 } 190 191 fun getRestoredTilePosition(tile: String): Int = 192 restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END 193 194 /** Returns `true` if the tile has been auto-added before */ 195 fun isAdded(tile: String): Boolean { 196 return synchronized(autoAdded) { tile in autoAdded } 197 } 198 199 /** 200 * Sets a tile as auto-added. 201 * 202 * From here on, [isAdded] will return true for that tile. 203 */ 204 fun setTileAdded(tile: String) { 205 val tiles = 206 synchronized(autoAdded) { 207 if (autoAdded.add(tile)) { 208 getTilesFromListLocked() 209 } else { 210 null 211 } 212 } 213 tiles?.let { saveTiles(it) } 214 } 215 216 /** 217 * Removes a tile from the list of auto-added. 218 * 219 * This allows for this tile to be auto-added again in the future. 220 */ 221 fun setTileRemoved(tile: String) { 222 val tiles = 223 synchronized(autoAdded) { 224 if (autoAdded.remove(tile)) { 225 getTilesFromListLocked() 226 } else { 227 null 228 } 229 } 230 tiles?.let { saveTiles(it) } 231 } 232 233 private fun getTilesFromListLocked(): String { 234 return TextUtils.join(DELIMITER, autoAdded) 235 } 236 237 private fun saveTiles(tiles: String) { 238 secureSettings.putStringForUser( 239 Settings.Secure.QS_AUTO_ADDED_TILES, 240 tiles, 241 /* tag */ null, 242 /* makeDefault */ false, 243 userId, 244 /* overrideableByRestore */ true 245 ) 246 } 247 248 private fun loadTiles() { 249 synchronized(autoAdded) { 250 autoAdded.clear() 251 autoAdded.addAll(getAdded()) 252 } 253 } 254 255 private fun getAdded(): Collection<String> { 256 val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId) 257 return current?.split(DELIMITER) ?: emptySet() 258 } 259 260 override fun dump(pw: PrintWriter, args: Array<out String>) { 261 pw.println("Current user: $userId") 262 pw.println("Restored tiles: $restoredTiles") 263 pw.println("Added tiles: $autoAdded") 264 } 265 266 @SysUISingleton 267 class Builder 268 @Inject 269 constructor( 270 private val secureSettings: SecureSettings, 271 private val broadcastDispatcher: BroadcastDispatcher, 272 private val qsHost: QSHost, 273 private val dumpManager: DumpManager, 274 @Main private val handler: Handler, 275 @Background private val executor: Executor 276 ) { 277 private var userId: Int = 0 278 279 fun setUserId(_userId: Int): Builder { 280 userId = _userId 281 return this 282 } 283 284 fun build(): AutoAddTracker { 285 return AutoAddTracker( 286 secureSettings, 287 broadcastDispatcher, 288 qsHost, 289 dumpManager, 290 handler, 291 executor, 292 userId 293 ) 294 } 295 } 296 297 private data class AutoTile(val index: Int, val tileType: String) 298 } 299