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 88 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 103 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 120 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 131 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 146 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 165 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 177 override fun addCallback(callback: UserTracker.Callback, executor: Executor) { 178 synchronized(callbacks) { 179 callbacks.add(DataItem(WeakReference(callback), executor)) 180 } 181 } 182 183 override fun removeCallback(callback: UserTracker.Callback) { 184 synchronized(callbacks) { 185 callbacks.removeIf { it.sameOrEmpty(callback) } 186 } 187 } 188 189 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 202 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") 225 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") 233 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 ) { 243 fun sameOrEmpty(other: UserTracker.Callback): Boolean { 244 return callback.get()?.equals(other) ?: true 245 } 246 }