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