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