• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         resumeSessionReceiver.register()
74         resetOrExitSessionReceiver.register()
75     }
76 
77     /** Notifies that the device has finished booting. */
78     fun onDeviceBootCompleted() {
79         applicationScope.launch {
80             if (isDeviceAllowedToAddGuest()) {
81                 guaranteePresent()
82                 return@launch
83             }
84 
85             suspendCancellableCoroutine<Unit> { continuation ->
86                 val callback =
87                     object : DeviceProvisionedController.DeviceProvisionedListener {
88                         override fun onDeviceProvisionedChanged() {
89                             continuation.resumeWith(Result.success(Unit))
90                             deviceProvisionedController.removeCallback(this)
91                         }
92                     }
93 
94                 deviceProvisionedController.addCallback(callback)
95             }
96 
97             if (isDeviceAllowedToAddGuest()) {
98                 guaranteePresent()
99             }
100         }
101     }
102 
103     /** Creates a guest user and switches to it. */
104     fun createAndSwitchTo(
105         showDialog: (ShowDialogRequestModel) -> Unit,
106         dismissDialog: () -> Unit,
107         selectUser: (userId: Int) -> Unit,
108     ) {
109         applicationScope.launch {
110             val newGuestUserId = create(showDialog, dismissDialog)
111             if (newGuestUserId != UserHandle.USER_NULL) {
112                 selectUser(newGuestUserId)
113             }
114         }
115     }
116 
117     /** Exits the guest user, switching back to the last non-guest user or to the default user. */
118     fun exit(
119         @UserIdInt guestUserId: Int,
120         @UserIdInt targetUserId: Int,
121         forceRemoveGuestOnExit: Boolean,
122         showDialog: (ShowDialogRequestModel) -> Unit,
123         dismissDialog: () -> Unit,
124         switchUser: (userId: Int) -> Unit,
125     ) {
126         val currentUserInfo = repository.getSelectedUserInfo()
127         if (currentUserInfo.id != guestUserId) {
128             Log.w(
129                 TAG,
130                 "User requesting to start a new session ($guestUserId) is not current user" +
131                     " (${currentUserInfo.id})"
132             )
133             return
134         }
135 
136         if (!currentUserInfo.isGuest) {
137             Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
138             return
139         }
140 
141         applicationScope.launch {
142             var newUserId = UserHandle.USER_SYSTEM
143             if (targetUserId == UserHandle.USER_NULL) {
144                 // When a target user is not specified switch to last non guest user:
145                 val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
146                 if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
147                     val info =
148                         withContext(backgroundDispatcher) {
149                             manager.getUserInfo(lastSelectedNonGuestUserHandle)
150                         }
151                     if (info != null && info.isEnabled && info.supportsSwitchToByUser()) {
152                         newUserId = info.id
153                     }
154                 }
155             } else {
156                 newUserId = targetUserId
157             }
158 
159             if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) {
160                 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE)
161                 remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser)
162             } else {
163                 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH)
164                 switchUser(newUserId)
165             }
166         }
167     }
168 
169     /**
170      * Guarantees that the guest user is present on the device, creating it if needed and if allowed
171      * to.
172      */
173     suspend fun guaranteePresent() {
174         if (!isDeviceAllowedToAddGuest()) {
175             return
176         }
177 
178         val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() }
179         if (guestUser == null) {
180             scheduleCreation()
181         }
182     }
183 
184     /** Removes the guest user from the device. */
185     suspend fun remove(
186         @UserIdInt guestUserId: Int,
187         @UserIdInt targetUserId: Int,
188         showDialog: (ShowDialogRequestModel) -> Unit,
189         dismissDialog: () -> Unit,
190         switchUser: (userId: Int) -> Unit,
191     ) {
192         val currentUser: UserInfo = repository.getSelectedUserInfo()
193         if (currentUser.id != guestUserId) {
194             Log.w(
195                 TAG,
196                 "User requesting to start a new session ($guestUserId) is not current user" +
197                     " ($currentUser.id)"
198             )
199             return
200         }
201 
202         if (!currentUser.isGuest) {
203             Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
204             return
205         }
206 
207         val marked =
208             withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) }
209         if (!marked) {
210             Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId")
211             return
212         }
213 
214         if (targetUserId == UserHandle.USER_NULL) {
215             // Create a new guest in the foreground, and then immediately switch to it
216             val newGuestId = create(showDialog, dismissDialog)
217             if (newGuestId == UserHandle.USER_NULL) {
218                 Log.e(TAG, "Could not create new guest, switching back to system user")
219                 switchUser(UserHandle.USER_SYSTEM)
220                 withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
221                 try {
222                     WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
223                 } catch (e: RemoteException) {
224                     Log.e(
225                         TAG,
226                         "Couldn't remove guest because ActivityManager or WindowManager is dead"
227                     )
228                 }
229                 return
230             }
231 
232             switchUser(newGuestId)
233 
234             withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
235         } else {
236             if (repository.isGuestUserAutoCreated) {
237                 repository.isGuestUserResetting = true
238             }
239             switchUser(targetUserId)
240             manager.removeUser(currentUser.id)
241         }
242     }
243 
244     /**
245      * Creates the guest user and adds it to the device.
246      *
247      * @param showDialog A function to invoke to show a dialog.
248      * @param dismissDialog A function to invoke to dismiss a dialog.
249      * @return The user ID of the newly-created guest user.
250      */
251     private suspend fun create(
252         showDialog: (ShowDialogRequestModel) -> Unit,
253         dismissDialog: () -> Unit,
254     ): Int {
255         return withContext(mainDispatcher) {
256             showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
257             val guestUserId = createInBackground()
258             dismissDialog()
259             if (guestUserId != UserHandle.USER_NULL) {
260                 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD)
261             } else {
262                 Toast.makeText(
263                         applicationContext,
264                         com.android.settingslib.R.string.add_guest_failed,
265                         Toast.LENGTH_SHORT,
266                     )
267                     .show()
268             }
269 
270             guestUserId
271         }
272     }
273 
274     /** Schedules the creation of the guest user. */
275     private suspend fun scheduleCreation() {
276         if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) {
277             return
278         }
279 
280         withContext(backgroundDispatcher) {
281             val newGuestUserId = createInBackground()
282             repository.isGuestUserCreationScheduled.set(false)
283             repository.isGuestUserResetting = false
284             if (newGuestUserId == UserHandle.USER_NULL) {
285                 Log.w(TAG, "Could not create new guest while exiting existing guest")
286                 // Refresh users so that we still display "Guest" if
287                 // config_guestUserAutoCreated=true
288                 refreshUsersScheduler.refreshIfNotPaused()
289             }
290         }
291     }
292 
293     /**
294      * Creates a guest user and return its multi-user user ID.
295      *
296      * This method does not check if a guest already exists before it makes a call to [UserManager]
297      * to create a new one.
298      *
299      * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if
300      *   the guest couldn't be created.
301      */
302     @UserIdInt
303     private suspend fun createInBackground(): Int {
304         return withContext(backgroundDispatcher) {
305             try {
306                 val guestUser = manager.createGuest(applicationContext)
307                 if (guestUser != null) {
308                     guestUser.id
309                 } else {
310                     Log.e(
311                         TAG,
312                         "Couldn't create guest, most likely because there already exists one!"
313                     )
314                     UserHandle.USER_NULL
315                 }
316             } catch (e: UserManager.UserOperationException) {
317                 Log.e(TAG, "Couldn't create guest user!", e)
318                 UserHandle.USER_NULL
319             }
320         }
321     }
322 
323     private fun isDeviceAllowedToAddGuest(): Boolean {
324         return deviceProvisionedController.isDeviceProvisioned &&
325             !devicePolicyManager.isDeviceManaged
326     }
327 
328     companion object {
329         private const val TAG = "GuestUserInteractor"
330     }
331 }
332