1 /*
<lambda>null2  * Copyright (C) 2022 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 
18 package com.android.systemui.user.domain.interactor
19 
20 import android.annotation.UserIdInt
21 import android.app.admin.DevicePolicyManager
22 import android.content.Context
23 import android.content.pm.UserInfo
24 import android.os.RemoteException
25 import android.os.UserHandle
26 import android.os.UserManager
27 import android.util.Log
28 import android.view.WindowManagerGlobal
29 import android.widget.Toast
30 import com.android.internal.logging.UiEventLogger
31 import com.android.systemui.GuestResetOrExitSessionReceiver
32 import com.android.systemui.GuestResumeSessionReceiver
33 import com.android.systemui.dagger.SysUISingleton
34 import com.android.systemui.dagger.qualifiers.Application
35 import com.android.systemui.dagger.qualifiers.Background
36 import com.android.systemui.dagger.qualifiers.Main
37 import com.android.systemui.qs.QSUserSwitcherEvent
38 import com.android.systemui.statusbar.policy.DeviceProvisionedController
39 import com.android.systemui.user.data.repository.UserRepository
40 import com.android.systemui.user.domain.model.ShowDialogRequestModel
41 import javax.inject.Inject
42 import kotlinx.coroutines.CoroutineDispatcher
43 import kotlinx.coroutines.CoroutineScope
44 import kotlinx.coroutines.launch
45 import kotlinx.coroutines.suspendCancellableCoroutine
46 import kotlinx.coroutines.withContext
47 
48 /** Encapsulates business logic to interact with guest user data and systems. */
49 @SysUISingleton
50 class GuestUserInteractor
51 @Inject
52 constructor(
53     @Application private val applicationContext: Context,
54     @Application private val applicationScope: CoroutineScope,
55     @Main private val mainDispatcher: CoroutineDispatcher,
56     @Background private val backgroundDispatcher: CoroutineDispatcher,
57     private val manager: UserManager,
58     private val repository: UserRepository,
59     private val deviceProvisionedController: DeviceProvisionedController,
60     private val devicePolicyManager: DevicePolicyManager,
61     private val refreshUsersScheduler: RefreshUsersScheduler,
62     private val uiEventLogger: UiEventLogger,
63     resumeSessionReceiver: GuestResumeSessionReceiver,
64     resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver,
65 ) {
66     /** Whether the device is configured to always have a guest user available. */
67     val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
68 
69     /** Whether the guest user is currently being reset. */
70     val isGuestUserResetting: Boolean = repository.isGuestUserResetting
71 
72     init {
73         if (applicationContext.userId == UserHandle.USER_SYSTEM) {
74             resumeSessionReceiver.register()
75             resetOrExitSessionReceiver.register()
76         }
77     }
78 
79     /** Notifies that the device has finished booting. */
80     fun onDeviceBootCompleted() {
81         applicationScope.launch {
82             if (isDeviceAllowedToAddGuest()) {
83                 guaranteePresent()
84                 return@launch
85             }
86 
87             suspendCancellableCoroutine<Unit> { continuation ->
88                 val callback =
89                     object : DeviceProvisionedController.DeviceProvisionedListener {
90                         override fun onDeviceProvisionedChanged() {
91                             continuation.resumeWith(Result.success(Unit))
92                             deviceProvisionedController.removeCallback(this)
93                         }
94                     }
95 
96                 deviceProvisionedController.addCallback(callback)
97             }
98 
99             if (isDeviceAllowedToAddGuest()) {
100                 guaranteePresent()
101             }
102         }
103     }
104 
105     /** Creates a guest user and switches to it. */
106     fun createAndSwitchTo(
107         showDialog: (ShowDialogRequestModel) -> Unit,
108         dismissDialog: () -> Unit,
109         selectUser: (userId: Int) -> Unit,
110     ) {
111         applicationScope.launch {
112             val newGuestUserId = create(showDialog, dismissDialog)
113             if (newGuestUserId != UserHandle.USER_NULL) {
114                 selectUser(newGuestUserId)
115             }
116         }
117     }
118 
119     /** Exits the guest user, switching back to the last non-guest user or to the default user. */
120     fun exit(
121         @UserIdInt guestUserId: Int,
122         @UserIdInt targetUserId: Int,
123         forceRemoveGuestOnExit: Boolean,
124         showDialog: (ShowDialogRequestModel) -> Unit,
125         dismissDialog: () -> Unit,
126         switchUser: (userId: Int) -> Unit,
127     ) {
128         val currentUserInfo = repository.getSelectedUserInfo()
129         if (currentUserInfo.id != guestUserId) {
130             Log.w(
131                 TAG,
132                 "User requesting to start a new session ($guestUserId) is not current user" +
133                     " (${currentUserInfo.id})"
134             )
135             return
136         }
137 
138         if (!currentUserInfo.isGuest) {
139             Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
140             return
141         }
142 
143         applicationScope.launch {
144             var newUserId = repository.mainUserId
145             if (targetUserId == UserHandle.USER_NULL) {
146                 // When a target user is not specified switch to last non guest user:
147                 val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
148                 if (lastSelectedNonGuestUserHandle != repository.mainUserId) {
149                     val info =
150                         withContext(backgroundDispatcher) {
151                             manager.getUserInfo(lastSelectedNonGuestUserHandle)
152                         }
153                     if (info != null && info.isEnabled && info.supportsSwitchTo()) {
154                         newUserId = info.id
155                     }
156                 }
157             } else {
158                 newUserId = targetUserId
159             }
160 
161             if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) {
162                 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE)
163                 remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser)
164             } else {
165                 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH)
166                 switchUser(newUserId)
167             }
168         }
169     }
170 
171     /**
172      * Guarantees that the guest user is present on the device, creating it if needed and if allowed
173      * to.
174      */
175     suspend fun guaranteePresent() {
176         if (!isDeviceAllowedToAddGuest()) {
177             return
178         }
179 
180         val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() }
181         if (guestUser == null) {
182             scheduleCreation()
183         }
184     }
185 
186     /** Removes the guest user from the device. */
187     suspend fun remove(
188         @UserIdInt guestUserId: Int,
189         @UserIdInt targetUserId: Int,
190         showDialog: (ShowDialogRequestModel) -> Unit,
191         dismissDialog: () -> Unit,
192         switchUser: (userId: Int) -> Unit,
193     ) {
194         val currentUser: UserInfo = repository.getSelectedUserInfo()
195         if (currentUser.id != guestUserId) {
196             Log.w(
197                 TAG,
198                 "User requesting to start a new session ($guestUserId) is not current user" +
199                     " ($currentUser.id)"
200             )
201             return
202         }
203 
204         if (!currentUser.isGuest) {
205             Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
206             return
207         }
208 
209         val marked =
210             withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) }
211         if (!marked) {
212             Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId")
213             return
214         }
215 
216         if (targetUserId == UserHandle.USER_NULL) {
217             // Create a new guest in the foreground, and then immediately switch to it
218             val newGuestId = create(showDialog, dismissDialog)
219             if (newGuestId == UserHandle.USER_NULL) {
220                 Log.e(TAG, "Could not create new guest, switching back to main user")
221                 val mainUser = withContext(backgroundDispatcher) { manager.mainUser?.identifier }
222 
223                 mainUser?.let { switchUser(it) }
224 
225                 withContext(backgroundDispatcher) {
226                     manager.removeUserWhenPossible(
227                         UserHandle.of(currentUser.id),
228                         /* overrideDevicePolicy= */ false
229                     )
230                 }
231                 try {
232                     checkNotNull(WindowManagerGlobal.getWindowManagerService())
233                         .lockNow(/* options= */ null)
234                 } catch (e: RemoteException) {
235                     Log.e(
236                         TAG,
237                         "Couldn't remove guest because ActivityManager or WindowManager is dead"
238                     )
239                 }
240                 return
241             }
242 
243             switchUser(newGuestId)
244 
245             withContext(backgroundDispatcher) {
246                 manager.removeUserWhenPossible(
247                     UserHandle.of(currentUser.id),
248                     /* overrideDevicePolicy= */ false
249                 )
250             }
251         } else {
252             if (repository.isGuestUserAutoCreated) {
253                 repository.isGuestUserResetting = true
254             }
255             switchUser(targetUserId)
256             manager.removeUserWhenPossible(
257                 UserHandle.of(currentUser.id),
258                 /* overrideDevicePolicy= */ false
259             )
260         }
261     }
262 
263     /**
264      * Creates the guest user and adds it to the device.
265      *
266      * @param showDialog A function to invoke to show a dialog.
267      * @param dismissDialog A function to invoke to dismiss a dialog.
268      * @return The user ID of the newly-created guest user.
269      */
270     private suspend fun create(
271         showDialog: (ShowDialogRequestModel) -> Unit,
272         dismissDialog: () -> Unit,
273     ): Int {
274         return withContext(mainDispatcher) {
275             showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
276             val guestUserId = createInBackground()
277             dismissDialog()
278             if (guestUserId != UserHandle.USER_NULL) {
279                 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD)
280             } else {
281                 Toast.makeText(
282                         applicationContext,
283                         com.android.settingslib.R.string.add_guest_failed,
284                         Toast.LENGTH_SHORT,
285                     )
286                     .show()
287             }
288 
289             guestUserId
290         }
291     }
292 
293     /** Schedules the creation of the guest user. */
294     private suspend fun scheduleCreation() {
295         if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) {
296             return
297         }
298 
299         withContext(backgroundDispatcher) {
300             val newGuestUserId = createInBackground()
301             repository.isGuestUserCreationScheduled.set(false)
302             repository.isGuestUserResetting = false
303             if (newGuestUserId == UserHandle.USER_NULL) {
304                 Log.w(TAG, "Could not create new guest while exiting existing guest")
305                 // Refresh users so that we still display "Guest" if
306                 // config_guestUserAutoCreated=true
307                 refreshUsersScheduler.refreshIfNotPaused()
308             }
309         }
310     }
311 
312     /**
313      * Creates a guest user and return its multi-user user ID.
314      *
315      * This method does not check if a guest already exists before it makes a call to [UserManager]
316      * to create a new one.
317      *
318      * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if
319      *   the guest couldn't be created.
320      */
321     @UserIdInt
322     private suspend fun createInBackground(): Int {
323         return withContext(backgroundDispatcher) {
324             try {
325                 val guestUser = manager.createGuest(applicationContext)
326                 if (guestUser != null) {
327                     guestUser.id
328                 } else {
329                     Log.e(
330                         TAG,
331                         "Couldn't create guest, most likely because there already exists one!"
332                     )
333                     UserHandle.USER_NULL
334                 }
335             } catch (e: UserManager.UserOperationException) {
336                 Log.e(TAG, "Couldn't create guest user!", e)
337                 UserHandle.USER_NULL
338             }
339         }
340     }
341 
342     private fun isDeviceAllowedToAddGuest(): Boolean {
343         return deviceProvisionedController.isDeviceProvisioned &&
344             !devicePolicyManager.isDeviceManaged
345     }
346 
347     companion object {
348         private const val TAG = "GuestUserInteractor"
349     }
350 }
351