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 kotlinx.coroutines.CoroutineDispatcher
40 import kotlinx.coroutines.CoroutineScope
41 import kotlinx.coroutines.Job
42 import kotlinx.coroutines.asCoroutineDispatcher
43 import kotlinx.coroutines.coroutineScope
44 import kotlinx.coroutines.delay
45 import kotlinx.coroutines.launch
46 import kotlinx.coroutines.sync.Mutex
47 import java.io.PrintWriter
48 import java.lang.ref.WeakReference
49 import java.util.concurrent.CountDownLatch
50 import java.util.concurrent.Executor
51 import javax.inject.Provider
52 import kotlin.properties.ReadWriteProperty
53 import kotlin.reflect.KProperty
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
59  * modify 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).
63  * Other classes that want to listen to the broadcasts listened here SHOULD
64  * subscribe to this class instead.
65  *
66  * @see UserTracker
67  *
68  * Class constructed and initialized in [SettingsModule].
69  */
70 open class UserTrackerImpl 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 get() =
91         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 val userInfo: UserInfo
107         get() {
108             val user = userId
109             return userProfiles.first { it.id == user }
110         }
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")
122     private val callbacks: MutableList<DataItem> = ArrayList()
123 
124     private var userSwitchingJob: Job? = null
125     private var afterUserSwitchingJob: Job? = null
126 
127     open fun initialize(startingUser: Int) {
128         if (initialized) {
129             return
130         }
131         initialized = true
132         setUserIdInternal(startingUser)
133 
134         val filter = IntentFilter().apply {
135             addAction(Intent.ACTION_LOCALE_CHANGED)
136             addAction(Intent.ACTION_USER_INFO_CHANGED)
137             addAction(Intent.ACTION_PROFILE_ADDED)
138             addAction(Intent.ACTION_PROFILE_REMOVED)
139             addAction(Intent.ACTION_PROFILE_AVAILABLE)
140             addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
141             // These get called when a managed profile goes in or out of quiet mode.
142             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
143             addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
144             addAction(Intent.ACTION_MANAGED_PROFILE_ADDED)
145             addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
146             addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
147         }
148         context.registerReceiverForAllUsers(this, filter, null, backgroundHandler)
149 
150         registerUserSwitchObserver()
151 
152         dumpManager.registerDumpable(TAG, this)
153     }
154 
155     override fun onReceive(context: Context, intent: Intent) {
156         when (intent.action) {
157             Intent.ACTION_LOCALE_CHANGED,
158             Intent.ACTION_USER_INFO_CHANGED,
159             Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
160             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
161             Intent.ACTION_MANAGED_PROFILE_ADDED,
162             Intent.ACTION_MANAGED_PROFILE_REMOVED,
163             Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
164             Intent.ACTION_PROFILE_ADDED,
165             Intent.ACTION_PROFILE_REMOVED,
166             Intent.ACTION_PROFILE_AVAILABLE,
167             Intent.ACTION_PROFILE_UNAVAILABLE -> {
168                 handleProfilesChanged()
169             }
170         }
171     }
172 
173     override fun createCurrentUserContext(context: Context): Context {
174         synchronized(mutex) {
175             return context.createContextAsUser(userHandle, 0)
176         }
177     }
178 
179     private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> {
180         val profiles = userManager.getProfiles(user)
181         val handle = UserHandle(user)
182         val ctx = context.createContextAsUser(handle, 0)
183 
184         synchronized(mutex) {
185             userId = user
186             userHandle = handle
187             userContext = ctx
188             userProfiles = profiles.map { UserInfo(it) }
189         }
190         return ctx to profiles
191     }
192 
193     private fun registerUserSwitchObserver() {
194         iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
195             override fun onBeforeUserSwitching(newUserId: Int) {
196                 handleBeforeUserSwitching(newUserId)
197             }
198 
199             override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
200                 if (isBackgroundUserSwitchEnabled) {
201                     userSwitchingJob?.cancel()
202                     userSwitchingJob = appScope.launch(backgroundContext) {
203                         handleUserSwitchingCoroutines(newUserId) {
204                             reply?.sendResult(null)
205                         }
206                     }
207                 } else {
208                     handleUserSwitching(newUserId)
209                     reply?.sendResult(null)
210                 }
211             }
212 
213             override fun onUserSwitchComplete(newUserId: Int) {
214                 if (isBackgroundUserSwitchEnabled) {
215                     afterUserSwitchingJob?.cancel()
216                     afterUserSwitchingJob = appScope.launch(backgroundContext) {
217                         handleUserSwitchComplete(newUserId)
218                     }
219                 } else {
220                     handleUserSwitchComplete(newUserId)
221                 }
222             }
223         }, TAG)
224     }
225 
226     @WorkerThread
227     protected open fun handleBeforeUserSwitching(newUserId: Int) {
228         setUserIdInternal(newUserId)
229 
230         notifySubscribers { callback, resultCallback ->
231             callback.onBeforeUserSwitching(newUserId)
232             resultCallback.run()
233         }.await()
234     }
235 
236     @WorkerThread
237     protected open fun handleUserSwitching(newUserId: Int) {
238         Assert.isNotMainThread()
239         Log.i(TAG, "Switching to user $newUserId")
240 
241         notifySubscribers { callback, resultCallback ->
242             callback.onUserChanging(newUserId, userContext, resultCallback)
243         }.await()
244     }
245 
246     @WorkerThread
247     protected open suspend fun handleUserSwitchingCoroutines(newUserId: Int, onDone: () -> Unit) =
248             coroutineScope {
249                 Assert.isNotMainThread()
250                 Log.i(TAG, "Switching to user $newUserId")
251 
252                 for (callbackDataItem in synchronized(callbacks) { callbacks.toList() }) {
253                     val callback: UserTracker.Callback = callbackDataItem.callback.get() ?: continue
254                     launch(callbackDataItem.executor.asCoroutineDispatcher()) {
255                         val mutex = Mutex(true)
256                         val thresholdLogJob = launch(backgroundContext) {
257                             delay(USER_CHANGE_THRESHOLD)
258                             Log.e(TAG, "Failed to finish $callback in time")
259                         }
260                         callback.onUserChanging(userId, userContext) { mutex.unlock() }
261                         mutex.lock()
262                         thresholdLogJob.cancel()
263                     }.join()
264                 }
265                 onDone()
266             }
267 
268     @WorkerThread
269     protected open fun handleUserSwitchComplete(newUserId: Int) {
270         Assert.isNotMainThread()
271         Log.i(TAG, "Switched to user $newUserId")
272 
273         notifySubscribers { callback, _ ->
274             callback.onUserChanged(newUserId, userContext)
275             callback.onProfilesChanged(userProfiles)
276         }
277     }
278 
279     @WorkerThread
280     protected open fun handleProfilesChanged() {
281         Assert.isNotMainThread()
282 
283         val profiles = userManager.getProfiles(userId)
284         synchronized(mutex) {
285             userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy
286         }
287         notifySubscribers { callback, _ ->
288             callback.onProfilesChanged(profiles)
289         }
290     }
291 
292     override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
293         synchronized(callbacks) {
294             callbacks.add(DataItem(WeakReference(callback), executor))
295         }
296     }
297 
298     override fun removeCallback(callback: UserTracker.Callback) {
299         synchronized(callbacks) {
300             callbacks.removeIf { it.sameOrEmpty(callback) }
301         }
302     }
303 
304     private inline fun notifySubscribers(
305             crossinline action: (UserTracker.Callback, resultCallback: Runnable) -> Unit
306     ): CountDownLatch {
307         val list = synchronized(callbacks) {
308             callbacks.toList()
309         }
310         val latch = CountDownLatch(list.size)
311         list.forEach {
312             val callback = it.callback.get()
313             if (callback != null) {
314                 it.executor.execute {
315                     action(callback) { latch.countDown() }
316                 }
317             } else {
318                 latch.countDown()
319             }
320         }
321         return latch
322     }
323 
324     override fun dump(pw: PrintWriter, args: Array<out String>) {
325         pw.println("Initialized: $initialized")
326         if (initialized) {
327             pw.println("userId: $userId")
328             val ids = userProfiles.map { it.toFullString() }
329             pw.println("userProfiles: $ids")
330         }
331         val list = synchronized(callbacks) {
332             callbacks.toList()
333         }
334         pw.println("Callbacks:")
335         list.forEach {
336             it.callback.get()?.let {
337                 pw.println("  $it")
338             }
339         }
340     }
341 
342     private class SynchronizedDelegate<T : Any>(
343         private var value: T
344     ) : ReadWriteProperty<UserTrackerImpl, T> {
345 
346         @GuardedBy("mutex")
347         override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T {
348             if (!thisRef.initialized) {
349                 throw IllegalStateException("Must initialize before getting ${property.name}")
350             }
351             return synchronized(thisRef.mutex) { value }
352         }
353 
354         @GuardedBy("mutex")
355         override fun setValue(thisRef: UserTrackerImpl, property: KProperty<*>, value: T) {
356             synchronized(thisRef.mutex) { this.value = value }
357         }
358     }
359 }
360 
361 private data class DataItem(
362     val callback: WeakReference<UserTracker.Callback>,
363     val executor: Executor
364 ) {
sameOrEmptynull365     fun sameOrEmpty(other: UserTracker.Callback): Boolean {
366         return callback.get()?.equals(other) ?: true
367     }
368 }
369