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