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