xref: /aosp_15_r20/frameworks/base/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
<lambda>null2  * Copyright (C) 2020 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.settings
18 
19 import android.app.IActivityManager
20 import android.app.UserSwitchObserver
21 import android.content.BroadcastReceiver
22 import android.content.ContentResolver
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.content.pm.UserInfo
27 import android.os.Handler
28 import android.os.IRemoteCallback
29 import android.os.UserHandle
30 import android.os.UserManager
31 import android.util.Log
32 import androidx.annotation.GuardedBy
33 import androidx.annotation.WorkerThread
34 import com.android.systemui.Dumpable
35 import com.android.systemui.dump.DumpManager
36 import com.android.systemui.flags.FeatureFlagsClassic
37 import com.android.systemui.flags.Flags
38 import com.android.systemui.util.Assert
39 import java.io.PrintWriter
40 import java.lang.ref.WeakReference
41 import java.util.concurrent.CountDownLatch
42 import java.util.concurrent.Executor
43 import javax.inject.Provider
44 import kotlin.properties.ReadWriteProperty
45 import kotlin.reflect.KProperty
46 import kotlinx.coroutines.CoroutineDispatcher
47 import kotlinx.coroutines.CoroutineScope
48 import kotlinx.coroutines.Job
49 import kotlinx.coroutines.asCoroutineDispatcher
50 import kotlinx.coroutines.coroutineScope
51 import kotlinx.coroutines.delay
52 import com.android.app.tracing.coroutines.launchTraced as launch
53 import kotlinx.coroutines.sync.Mutex
54 
55 /**
56  * SystemUI cache for keeping track of the current user and associated values.
57  *
58  * The values provided asynchronously are NOT copies, but shared among all requesters. Do not modify
59  * them.
60  *
61  * This class purposefully doesn't use [BroadcastDispatcher] in order to receive the broadcast as
62  * soon as possible (and reduce its dependency graph). Other classes that want to listen to the
63  * broadcasts listened here SHOULD subscribe to this class instead.
64  *
65  * @see UserTracker
66  *
67  * Class constructed and initialized in [SettingsModule].
68  */
69 open class UserTrackerImpl
70 internal constructor(
71     private val context: Context,
72     private val featureFlagsProvider: Provider<FeatureFlagsClassic>,
73     private val userManager: UserManager,
74     private val iActivityManager: IActivityManager,
75     private val dumpManager: DumpManager,
76     private val appScope: CoroutineScope,
77     private val backgroundContext: CoroutineDispatcher,
78     private val backgroundHandler: Handler,
79 ) : UserTracker, Dumpable, BroadcastReceiver() {
80 
81     companion object {
82         private const val TAG = "UserTrackerImpl"
83         private const val USER_CHANGE_THRESHOLD = 5L * 1000 // 5 sec
84     }
85 
86     var initialized = false
87         private set
88 
89     private val mutex = Any()
90     private val isBackgroundUserSwitchEnabled: Boolean
91         get() = featureFlagsProvider.get().isEnabled(Flags.USER_TRACKER_BACKGROUND_CALLBACKS)
92 
93     @Deprecated("Use UserInteractor.getSelectedUserId()")
94     override var userId: Int by SynchronizedDelegate(context.userId)
95         protected set
96 
97     override var userHandle: UserHandle by SynchronizedDelegate(context.user)
98         protected set
99 
100     override var userContext: Context by SynchronizedDelegate(context)
101         protected set
102 
103     override val userContentResolver: ContentResolver
104         get() = userContext.contentResolver
105 
106     override var userInfo: UserInfo by SynchronizedDelegate(UserInfo(context.userId, "", 0))
107         protected set
108 
109     override var isUserSwitching = false
110         protected set
111 
112     /**
113      * Returns a [List<UserInfo>] of all profiles associated with the current user.
114      *
115      * The list returned is not a copy, so a copy should be made if its elements need to be
116      * modified.
117      */
118     override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList())
119         protected set
120 
121     @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList()
122 
123     private var userSwitchingJob: Job? = null
124     private var afterUserSwitchingJob: Job? = null
125 
126     open fun initialize(startingUser: Int) {
127         if (initialized) {
128             return
129         }
130         Log.i(TAG, "Starting user: $startingUser")
131         initialized = true
132         setUserIdInternal(startingUser)
133 
134         val filter =
135             IntentFilter().apply {
136                 addAction(Intent.ACTION_LOCALE_CHANGED)
137                 addAction(Intent.ACTION_USER_INFO_CHANGED)
138                 addAction(Intent.ACTION_PROFILE_ADDED)
139                 addAction(Intent.ACTION_PROFILE_REMOVED)
140                 addAction(Intent.ACTION_PROFILE_AVAILABLE)
141                 addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
142                 // These get called when a managed profile goes in or out of quiet mode.
143                 addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
144                 addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
145                 addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
146                 addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
147                 addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
148             }
149         context.registerReceiverForAllUsers(this, filter, null, backgroundHandler)
150 
151         registerUserSwitchObserver()
152 
153         dumpManager.registerDumpable(TAG, this)
154     }
155 
156     override fun onReceive(context: Context, intent: Intent) {
157         when (intent.action) {
158             Intent.ACTION_LOCALE_CHANGED,
159             Intent.ACTION_USER_INFO_CHANGED,
160             Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
161             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
162             Intent.ACTION_MANAGED_PROFILE_ADDED,
163             Intent.ACTION_MANAGED_PROFILE_REMOVED,
164             Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
165             Intent.ACTION_PROFILE_ADDED,
166             Intent.ACTION_PROFILE_REMOVED,
167             Intent.ACTION_PROFILE_AVAILABLE,
168             Intent.ACTION_PROFILE_UNAVAILABLE -> {
169                 handleProfilesChanged()
170             }
171         }
172     }
173 
174     override fun createCurrentUserContext(context: Context): Context {
175         synchronized(mutex) {
176             return context.createContextAsUser(userHandle, 0)
177         }
178     }
179 
180     private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> {
181         val profiles = userManager.getProfiles(user)
182         val handle = UserHandle(user)
183         val ctx = context.createContextAsUser(handle, 0)
184 
185         synchronized(mutex) {
186             userId = user
187             userHandle = handle
188             userContext = ctx
189             userProfiles = profiles.map { UserInfo(it) }
190             userInfo = profiles.first { it.id == user }
191         }
192         return ctx to profiles
193     }
194 
195     private fun registerUserSwitchObserver() {
196         iActivityManager.registerUserSwitchObserver(
197             object : UserSwitchObserver() {
198                 override fun onBeforeUserSwitching(newUserId: Int) {
199                     handleBeforeUserSwitching(newUserId)
200                 }
201 
202                 override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
203                     isUserSwitching = true
204                     if (isBackgroundUserSwitchEnabled) {
205                         userSwitchingJob?.cancel()
206                         userSwitchingJob =
207                             appScope.launch(context = backgroundContext) {
208                                 handleUserSwitchingCoroutines(newUserId) { reply?.sendResult(null) }
209                             }
210                     } else {
211                         handleUserSwitching(newUserId)
212                         reply?.sendResult(null)
213                     }
214                 }
215 
216                 override fun onUserSwitchComplete(newUserId: Int) {
217                     isUserSwitching = false
218                     if (isBackgroundUserSwitchEnabled) {
219                         afterUserSwitchingJob?.cancel()
220                         afterUserSwitchingJob =
221                             appScope.launch(context = backgroundContext) {
222                                 handleUserSwitchComplete(newUserId)
223                             }
224                     } else {
225                         handleUserSwitchComplete(newUserId)
226                     }
227                 }
228             },
229             TAG,
230         )
231     }
232 
233     @WorkerThread
234     protected open fun handleBeforeUserSwitching(newUserId: Int) {
235         setUserIdInternal(newUserId)
236 
237         notifySubscribers { callback, resultCallback ->
238                 callback.onBeforeUserSwitching(newUserId)
239                 resultCallback.run()
240             }
241             .await()
242     }
243 
244     @WorkerThread
245     protected open fun handleUserSwitching(newUserId: Int) {
246         Assert.isNotMainThread()
247         Log.i(TAG, "Switching to user $newUserId")
248 
249         notifySubscribers { callback, resultCallback ->
250                 callback.onUserChanging(newUserId, userContext, resultCallback)
251             }
252             .await()
253     }
254 
255     @WorkerThread
256     protected open suspend fun handleUserSwitchingCoroutines(newUserId: Int, onDone: () -> Unit) =
257         coroutineScope {
258             Assert.isNotMainThread()
259             Log.i(TAG, "Switching to user $newUserId")
260 
261             for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) {
262                 val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue
263                 launch(context = callbackDataItem.executor.asCoroutineDispatcher()) {
264                         val mutex = Mutex(true)
265                         val thresholdLogJob =
266                             launch(context = backgroundContext) {
267                                 delay(USER_CHANGE_THRESHOLD)
268                                 Log.e(TAG, "Failed to finish $callback in time")
269                             }
270                         callback.onUserChanging(userId, userContext) { mutex.unlock() }
271                         mutex.lock()
272                         thresholdLogJob.cancel()
273                     }
274                     .join()
275             }
276             onDone()
277         }
278 
279     @WorkerThread
280     protected open fun handleUserSwitchComplete(newUserId: Int) {
281         Assert.isNotMainThread()
282         Log.i(TAG, "Switched to user $newUserId")
283 
284         notifySubscribers { callback, _ ->
285             callback.onUserChanged(newUserId, userContext)
286             callback.onProfilesChanged(userProfiles)
287         }
288     }
289 
290     @WorkerThread
291     protected open fun handleProfilesChanged() {
292         Assert.isNotMainThread()
293 
294         val profiles = userManager.getProfiles(userId)
295         synchronized(mutex) {
296             userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy
297         }
298         notifySubscribers { callback, _ -> callback.onProfilesChanged(profiles) }
299     }
300 
301     override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
302         synchronized(callbacks) { callbacks.add(DataItem(WeakReference(callback), executor)) }
303     }
304 
305     override fun removeCallback(callback: UserTracker.Callback) {
306         synchronized(callbacks) { callbacks.removeIf { it.sameOrEmpty(callback) } }
307     }
308 
309     private inline fun notifySubscribers(
310         crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit
311     ): CountDownLatch {
312         val list = synchronized(callbacks) { callbacks.toList() }
313         val latch = CountDownLatch(list.size)
314         list.forEach {
315             val callback = it.callback.get()
316             if (callback != null) {
317                 it.executor.execute { action(callback) { latch.countDown() } }
318             } else {
319                 latch.countDown()
320             }
321         }
322         return latch
323     }
324 
325     override fun dump(pw: PrintWriter, args: Array<out String>) {
326         pw.println("Initialized: $initialized")
327         if (initialized) {
328             pw.println("userId: $userId")
329             val ids = userProfiles.map { it.toFullString() }
330             pw.println("userProfiles: $ids")
331         }
332         val list = synchronized(callbacks) { callbacks.toList() }
333         pw.println("Callbacks:")
334         list.forEach { it.callback.get()?.let { pw.println("  $it") } }
335     }
336 
337     private class SynchronizedDelegate<T : Any>(private var value: T) :
338         ReadWriteProperty<UserTrackerImpl, T> {
339 
340         @GuardedBy("mutex")
341         override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T {
342             if (!thisRef.initialized) {
343                 throw IllegalStateException("Must initialize before getting ${property.name}")
344             }
345             return synchronized(thisRef.mutex) { value }
346         }
347 
348         @GuardedBy("mutex")
349         override fun setValue(thisRef: UserTrackerImpl, property: KProperty<*>, value: T) {
350             synchronized(thisRef.mutex) { this.value = value }
351         }
352     }
353 }
354 
355 private data class DataItem(
356     val callback: WeakReference<UserTracker.Callback>,
357     val executor: Executor,
358 ) {
sameOrEmptynull359     fun sameOrEmpty(other: UserTracker.Callback): Boolean {
360         return callback.get()?.equals(other) ?: true
361     }
362 }
363