1 /*
2  * 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.content.BroadcastReceiver
20 import android.content.ContentResolver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.content.pm.UserInfo
25 import android.os.Handler
26 import android.os.UserHandle
27 import android.os.UserManager
28 import android.util.Log
29 import androidx.annotation.GuardedBy
30 import androidx.annotation.WorkerThread
31 import com.android.systemui.Dumpable
32 import com.android.systemui.dump.DumpManager
33 import com.android.systemui.util.Assert
34 import java.io.FileDescriptor
35 import java.io.PrintWriter
36 import java.lang.IllegalStateException
37 import java.lang.ref.WeakReference
38 import java.util.concurrent.Executor
39 import kotlin.properties.ReadWriteProperty
40 import kotlin.reflect.KProperty
41 
42 /**
43  * SystemUI cache for keeping track of the current user and associated values.
44  *
45  * The values provided asynchronously are NOT copies, but shared among all requesters. Do not
46  * modify them.
47  *
48  * This class purposefully doesn't use [BroadcastDispatcher] in order to receive the broadcast as
49  * soon as possible (and reduce its dependency graph).
50  * Other classes that want to listen to the broadcasts listened here SHOULD
51  * subscribe to this class instead.
52  *
53  * @see UserTracker
54  *
55  * Class constructed and initialized in [SettingsModule].
56  */
57 class UserTrackerImpl internal constructor(
58     private val context: Context,
59     private val userManager: UserManager,
60     private val dumpManager: DumpManager,
61     private val backgroundHandler: Handler
62 ) : UserTracker, Dumpable, BroadcastReceiver() {
63 
64     companion object {
65         private const val TAG = "UserTrackerImpl"
66     }
67 
68     var initialized = false
69         private set
70 
71     private val mutex = Any()
72 
73     override var userId: Int by SynchronizedDelegate(context.userId)
74         private set
75 
76     override var userHandle: UserHandle by SynchronizedDelegate(context.user)
77         private set
78 
79     override var userContext: Context by SynchronizedDelegate(context)
80         private set
81 
82     override val userContentResolver: ContentResolver
83         get() = userContext.contentResolver
84 
85     override val userInfo: UserInfo
86         get() {
87             val user = userId
<lambda>null88             return userProfiles.first { it.id == user }
89         }
90 
91     /**
92      * Returns a [List<UserInfo>] of all profiles associated with the current user.
93      *
94      * The list returned is not a copy, so a copy should be made if its elements need to be
95      * modified.
96      */
97     override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList())
98         private set
99 
100     @GuardedBy("callbacks")
101     private val callbacks: MutableList<DataItem> = ArrayList()
102 
initializenull103     fun initialize(startingUser: Int) {
104         if (initialized) {
105             return
106         }
107         initialized = true
108         setUserIdInternal(startingUser)
109 
110         val filter = IntentFilter().apply {
111             addAction(Intent.ACTION_USER_SWITCHED)
112             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
113             addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)
114         }
115         context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler)
116 
117         dumpManager.registerDumpable(TAG, this)
118     }
119 
onReceivenull120     override fun onReceive(context: Context, intent: Intent) {
121         when (intent.action) {
122             Intent.ACTION_USER_SWITCHED -> {
123                 handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
124             }
125             Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> {
126                 handleProfilesChanged()
127             }
128         }
129     }
130 
setUserIdInternalnull131     private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> {
132         val profiles = userManager.getProfiles(user)
133         val handle = UserHandle(user)
134         val ctx = context.createContextAsUser(handle, 0)
135 
136         synchronized(mutex) {
137             userId = user
138             userHandle = handle
139             userContext = ctx
140             userProfiles = profiles.map { UserInfo(it) }
141         }
142         return ctx to profiles
143     }
144 
145     @WorkerThread
handleSwitchUsernull146     private fun handleSwitchUser(newUser: Int) {
147         Assert.isNotMainThread()
148         if (newUser == UserHandle.USER_NULL) {
149             Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
150             return
151         }
152 
153         if (newUser == userId) return
154         Log.i(TAG, "Switching to user $newUser")
155 
156         val (ctx, profiles) = setUserIdInternal(newUser)
157 
158         notifySubscribers {
159             onUserChanged(newUser, ctx)
160             onProfilesChanged(profiles)
161         }
162     }
163 
164     @WorkerThread
handleProfilesChangednull165     private fun handleProfilesChanged() {
166         Assert.isNotMainThread()
167 
168         val profiles = userManager.getProfiles(userId)
169         synchronized(mutex) {
170             userProfiles = profiles.map { UserInfo(it) } // save a "deep" copy
171         }
172         notifySubscribers {
173             onProfilesChanged(profiles)
174         }
175     }
176 
addCallbacknull177     override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
178         synchronized(callbacks) {
179             callbacks.add(DataItem(WeakReference(callback), executor))
180         }
181     }
182 
removeCallbacknull183     override fun removeCallback(callback: UserTracker.Callback) {
184         synchronized(callbacks) {
185             callbacks.removeIf { it.sameOrEmpty(callback) }
186         }
187     }
188 
notifySubscribersnull189     private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) {
190         val list = synchronized(callbacks) {
191             callbacks.toList()
192         }
193         list.forEach {
194             if (it.callback.get() != null) {
195                 it.executor.execute {
196                     it.callback.get()?.action()
197                 }
198             }
199         }
200     }
201 
dumpnull202     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
203         pw.println("Initialized: $initialized")
204         if (initialized) {
205             pw.println("userId: $userId")
206             val ids = userProfiles.map { it.id }
207             pw.println("userProfiles: $ids")
208         }
209         val list = synchronized(callbacks) {
210             callbacks.toList()
211         }
212         pw.println("Callbacks:")
213         list.forEach {
214             it.callback.get()?.let {
215                 pw.println("  $it")
216             }
217         }
218     }
219 
220     private class SynchronizedDelegate<T : Any>(
221         private var value: T
222     ) : ReadWriteProperty<UserTrackerImpl, T> {
223 
224         @GuardedBy("mutex")
getValuenull225         override fun getValue(thisRef: UserTrackerImpl, property: KProperty<*>): T {
226             if (!thisRef.initialized) {
227                 throw IllegalStateException("Must initialize before getting ${property.name}")
228             }
229             return synchronized(thisRef.mutex) { value }
230         }
231 
232         @GuardedBy("mutex")
setValuenull233         override fun setValue(thisRef: UserTrackerImpl, property: KProperty<*>, value: T) {
234             synchronized(thisRef.mutex) { this.value = value }
235         }
236     }
237 }
238 
239 private data class DataItem(
240     val callback: WeakReference<UserTracker.Callback>,
241     val executor: Executor
242 ) {
sameOrEmptynull243     fun sameOrEmpty(other: UserTracker.Callback): Boolean {
244         return callback.get()?.equals(other) ?: true
245     }
246 }