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 @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 qsHost.removeTiles(tilesToRemove) 124 } 125 val tiles = synchronized(autoAdded) { 126 autoAdded.clear() 127 autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) 128 getTilesFromListLocked() 129 } 130 saveTiles(tiles) 131 } ?: run { 132 Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + 133 "${Settings.Secure.QS_TILES} for user $userId") 134 } 135 } 136 else -> {} // Do nothing for other Settings 137 } 138 } 139 140 /** 141 * Init method must be called after construction to start listening 142 */ 143 fun initialize() { 144 dumpManager.registerDumpable(TAG, this) 145 loadTiles() 146 secureSettings.registerContentObserverForUser( 147 secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), 148 contentObserver, 149 UserHandle.USER_ALL 150 ) 151 registerBroadcastReceiver() 152 } 153 154 /** 155 * Unregister listeners, receivers and observers 156 */ 157 fun destroy() { 158 dumpManager.unregisterDumpable(TAG) 159 secureSettings.unregisterContentObserver(contentObserver) 160 unregisterBroadcastReceiver() 161 } 162 163 private fun registerBroadcastReceiver() { 164 broadcastDispatcher.registerReceiver( 165 restoreReceiver, 166 FILTER, 167 backgroundExecutor, 168 UserHandle.of(userId) 169 ) 170 } 171 172 private fun unregisterBroadcastReceiver() { 173 broadcastDispatcher.unregisterReceiver(restoreReceiver) 174 } 175 176 override fun changeUser(newUser: UserHandle) { 177 if (newUser.identifier == userId) return 178 unregisterBroadcastReceiver() 179 userId = newUser.identifier 180 restoredTiles = null 181 loadTiles() 182 registerBroadcastReceiver() 183 } 184 185 fun getRestoredTilePosition(tile: String): Int = 186 restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END 187 188 /** 189 * Returns `true` if the tile has been auto-added before 190 */ 191 fun isAdded(tile: String): Boolean { 192 return synchronized(autoAdded) { 193 tile in autoAdded 194 } 195 } 196 197 /** 198 * Sets a tile as auto-added. 199 * 200 * From here on, [isAdded] will return true for that tile. 201 */ 202 fun setTileAdded(tile: String) { 203 val tiles = synchronized(autoAdded) { 204 if (autoAdded.add(tile)) { 205 getTilesFromListLocked() 206 } else { 207 null 208 } 209 } 210 tiles?.let { saveTiles(it) } 211 } 212 213 /** 214 * Removes a tile from the list of auto-added. 215 * 216 * This allows for this tile to be auto-added again in the future. 217 */ 218 fun setTileRemoved(tile: String) { 219 val tiles = synchronized(autoAdded) { 220 if (autoAdded.remove(tile)) { 221 getTilesFromListLocked() 222 } else { 223 null 224 } 225 } 226 tiles?.let { saveTiles(it) } 227 } 228 229 private fun getTilesFromListLocked(): String { 230 return TextUtils.join(DELIMITER, autoAdded) 231 } 232 233 private fun saveTiles(tiles: String) { 234 secureSettings.putStringForUser( 235 Settings.Secure.QS_AUTO_ADDED_TILES, 236 tiles, 237 /* tag */ null, 238 /* makeDefault */ false, 239 userId, 240 /* overrideableByRestore */ true 241 ) 242 } 243 244 private fun loadTiles() { 245 synchronized(autoAdded) { 246 autoAdded.clear() 247 autoAdded.addAll(getAdded()) 248 } 249 } 250 251 private fun getAdded(): Collection<String> { 252 val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId) 253 return current?.split(DELIMITER) ?: emptySet() 254 } 255 256 override fun dump(pw: PrintWriter, args: Array<out String>) { 257 pw.println("Current user: $userId") 258 pw.println("Added tiles: $autoAdded") 259 } 260 261 @SysUISingleton 262 class Builder @Inject constructor( 263 private val secureSettings: SecureSettings, 264 private val broadcastDispatcher: BroadcastDispatcher, 265 private val qsHost: QSHost, 266 private val dumpManager: DumpManager, 267 @Main private val handler: Handler, 268 @Background private val executor: Executor 269 ) { 270 private var userId: Int = 0 271 272 fun setUserId(_userId: Int): Builder { 273 userId = _userId 274 return this 275 } 276 277 fun build(): AutoAddTracker { 278 return AutoAddTracker( 279 secureSettings, 280 broadcastDispatcher, 281 qsHost, 282 dumpManager, 283 handler, 284 executor, 285 userId 286 ) 287 } 288 } 289 290 private data class AutoTile(val index: Int, val tileType: String) 291 }