1 /* 2 * Copyright 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.photopicker.features.profileselector 18 19 import android.content.Context 20 import androidx.compose.runtime.getValue 21 import androidx.compose.runtime.setValue 22 import androidx.lifecycle.ViewModel 23 import androidx.lifecycle.viewModelScope 24 import com.android.photopicker.core.configuration.ConfigurationManager 25 import com.android.photopicker.core.events.Event 26 import com.android.photopicker.core.events.Events 27 import com.android.photopicker.core.events.Telemetry 28 import com.android.photopicker.core.features.FeatureToken 29 import com.android.photopicker.core.selection.Selection 30 import com.android.photopicker.core.user.SwitchUserProfileResult 31 import com.android.photopicker.core.user.UserMonitor 32 import com.android.photopicker.core.user.UserProfile 33 import com.android.photopicker.core.user.UserProfile.DisabledReason.QUIET_MODE_DO_NOT_SHOW 34 import com.android.photopicker.data.model.Media 35 import com.android.photopicker.extensions.getUserProfilesVisibleToPhotopicker 36 import dagger.hilt.android.lifecycle.HiltViewModel 37 import javax.inject.Inject 38 import kotlinx.coroutines.CoroutineScope 39 import kotlinx.coroutines.flow.SharingStarted 40 import kotlinx.coroutines.flow.StateFlow 41 import kotlinx.coroutines.flow.map 42 import kotlinx.coroutines.flow.stateIn 43 import kotlinx.coroutines.launch 44 45 /** 46 * The view model for the Profile selector, it provides various [UserStatus] and [UserProfile] flows 47 * to the compose UI, and handles switching profiles when a UI element is selected. 48 */ 49 @HiltViewModel 50 class ProfileSelectorViewModel 51 @Inject 52 constructor( 53 private val scopeOverride: CoroutineScope?, 54 private val selection: Selection<Media>, 55 private val userMonitor: UserMonitor, 56 private val events: Events, 57 private val configurationManager: ConfigurationManager, 58 ) : ViewModel() { 59 60 companion object { 61 val TAG: String = ProfileSelectorFeature.TAG 62 } 63 64 // Check if a scope override was injected before using the default [viewModelScope] 65 private val scope: CoroutineScope = 66 if (scopeOverride == null) { 67 this.viewModelScope 68 } else { 69 scopeOverride 70 } 71 72 /** All of the profiles that are available to Photopicker */ 73 val allProfiles: StateFlow<List<UserProfile>> = 74 userMonitor.userStatus 75 .getUserProfilesVisibleToPhotopicker() 76 .stateIn( 77 scope, 78 SharingStarted.WhileSubscribed(), 79 initialValue = <lambda>null80 userMonitor.userStatus.value.allProfiles.filterNot { 81 it.disabledReasons.contains(QUIET_MODE_DO_NOT_SHOW) 82 } 83 ) 84 85 /** The current active profile */ 86 val selectedProfile: StateFlow<UserProfile> = 87 userMonitor.userStatus <lambda>null88 .map { it.activeUserProfile } 89 .stateIn( 90 scope, 91 SharingStarted.WhileSubscribed(), 92 initialValue = userMonitor.userStatus.value.activeUserProfile 93 ) 94 95 /** 96 * Request for the profile to be changed to the provided profile. This is not guaranteed to 97 * succeed (the profile could be disabled/unavailable etc) 98 * 99 * If it does succeed, this will also clear out any selected media since media cannot be 100 * selected from multiple profiles simultaneously. 101 */ requestSwitchUsernull102 fun requestSwitchUser(context: Context, requested: UserProfile) { 103 scope.launch { 104 val result = userMonitor.requestSwitchActiveUserProfile(requested, context) 105 if (result == SwitchUserProfileResult.SUCCESS) { 106 // If the profile is actually changed, ensure the selection is cleared since 107 // content cannot be chosen from multiple profiles simultaneously. 108 selection.clear() 109 val configuration = configurationManager.configuration.value 110 // Log switching user profile in the picker 111 events.dispatch( 112 Event.LogPhotopickerUIEvent( 113 FeatureToken.PROFILE_SELECTOR.token, 114 configuration.sessionId, 115 configuration.callingPackageUid ?: -1, 116 Telemetry.UiEvent.SWITCH_USER_PROFILE 117 ) 118 ) 119 } 120 } 121 } 122 } 123