• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }