• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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