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 }