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