1 /* <lambda>null2 * Copyright (C) 2024 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.intentresolver.domain.interactor 18 19 import android.os.UserHandle 20 import com.android.intentresolver.data.repository.UserRepository 21 import com.android.intentresolver.inject.ApplicationUser 22 import com.android.intentresolver.shared.model.Profile 23 import com.android.intentresolver.shared.model.Profile.Type 24 import com.android.intentresolver.shared.model.User 25 import com.android.intentresolver.shared.model.User.Role 26 import javax.inject.Inject 27 import kotlinx.coroutines.flow.Flow 28 import kotlinx.coroutines.flow.combine 29 import kotlinx.coroutines.flow.map 30 31 /** The high level User interface. */ 32 class UserInteractor 33 @Inject 34 constructor( 35 private val userRepository: UserRepository, 36 /** The specific [User] of the application which started this one. */ 37 @ApplicationUser val launchedAs: UserHandle, 38 ) { 39 /** The profile group associated with the launching app user. */ 40 val profiles: Flow<List<Profile>> = 41 userRepository.users.map { users -> 42 users.mapNotNull { user -> 43 when (user.role) { 44 // PERSONAL includes CLONE 45 Role.PERSONAL -> { 46 Profile(Type.PERSONAL, user, users.firstOrNull { it.role == Role.CLONE }) 47 } 48 Role.CLONE -> { 49 /* ignore, included above */ 50 null 51 } 52 // others map 1:1 53 else -> Profile(profileFromRole(user.role), user) 54 } 55 } 56 } 57 58 /** The [Profile] of the application which started this one. */ 59 val launchedAsProfile: Flow<Profile> = 60 profiles.map { profiles -> 61 // The launching user profile is the one with a primary id or clone id 62 // matching the application user id. By definition there must always be exactly 63 // one matching profile for the current user. 64 profiles.single { 65 it.primary.id == launchedAs.identifier || it.clone?.id == launchedAs.identifier 66 } 67 } 68 /** 69 * Provides a flow to report on the availability of profile. An unavailable profile may be 70 * hidden or appear disabled within the app. 71 */ 72 val availability: Flow<Map<Profile, Boolean>> = 73 combine(profiles, userRepository.availability) { profiles, availability -> 74 profiles.associateWith { availability.getOrDefault(it.primary, false) } 75 } 76 77 /** 78 * Request the profile state be updated. In the case of enabling, the operation could take 79 * significant time and/or require user input. 80 */ 81 suspend fun updateState(profile: Profile, available: Boolean) { 82 userRepository.requestState(profile.primary, available) 83 } 84 85 private fun profileFromRole(role: Role): Type = 86 when (role) { 87 Role.PERSONAL -> Type.PERSONAL 88 Role.CLONE -> Type.PERSONAL /* CLONE maps to PERSONAL */ 89 Role.PRIVATE -> Type.PRIVATE 90 Role.WORK -> Type.WORK 91 } 92 } 93