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.keyguard.data.quickaffordance
19 
20 import android.content.Context
21 import android.os.UserHandle
22 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
23 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.settings.UserTracker
27 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
28 import javax.inject.Inject
29 import kotlinx.coroutines.CoroutineScope
30 import kotlinx.coroutines.ExperimentalCoroutinesApi
31 import kotlinx.coroutines.channels.awaitClose
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.SharingStarted
34 import kotlinx.coroutines.flow.StateFlow
35 import kotlinx.coroutines.flow.distinctUntilChanged
36 import kotlinx.coroutines.flow.emptyFlow
37 import kotlinx.coroutines.flow.flatMapLatest
38 import kotlinx.coroutines.flow.map
39 import kotlinx.coroutines.flow.stateIn
40 import kotlinx.coroutines.launch
41 
42 /**
43  * Manages and provides access to the current "selections" of keyguard quick affordances, answering
44  * the question "which affordances should the keyguard show?" for users associated with other System
45  * UI processes.
46  */
47 @OptIn(ExperimentalCoroutinesApi::class)
48 @SysUISingleton
49 class KeyguardQuickAffordanceRemoteUserSelectionManager
50 @Inject
51 constructor(
52     @Application private val scope: CoroutineScope,
53     private val userTracker: UserTracker,
54     private val clientFactory: KeyguardQuickAffordanceProviderClientFactory,
55     private val userHandle: UserHandle,
56 ) : KeyguardQuickAffordanceSelectionManager {
57 
58     private val userId: Flow<Int> = conflatedCallbackFlow {
59         val callback =
60             object : UserTracker.Callback {
61                 override fun onUserChanged(newUser: Int, userContext: Context) {
62                     trySendWithFailureLogging(newUser, TAG)
63                 }
64             }
65 
66         userTracker.addCallback(callback) { it.run() }
67         trySendWithFailureLogging(userTracker.userId, TAG)
68 
69         awaitClose { userTracker.removeCallback(callback) }
70     }
71 
72     private val clientOrNull: StateFlow<CustomizationProviderClient?> =
73         userId
74             .distinctUntilChanged()
75             .map { selectedUserId ->
76                 if (userHandle.isSystem && userHandle.identifier != selectedUserId) {
77                     clientFactory.create()
78                 } else {
79                     null
80                 }
81             }
82             .stateIn(
83                 scope = scope,
84                 started = SharingStarted.Eagerly,
85                 initialValue = null,
86             )
87 
88     private val _selections: StateFlow<Map<String, List<String>>> =
89         clientOrNull
90             .flatMapLatest { client ->
91                 client?.observeSelections()?.map { selections ->
92                     buildMap<String, List<String>> {
93                         selections.forEach { selection ->
94                             val slotId = selection.slotId
95                             val affordanceIds = (get(slotId) ?: emptyList()).toMutableList()
96                             affordanceIds.add(selection.affordanceId)
97                             put(slotId, affordanceIds)
98                         }
99                     }
100                 }
101                     ?: emptyFlow()
102             }
103             .stateIn(
104                 scope = scope,
105                 started = SharingStarted.Eagerly,
106                 initialValue = emptyMap(),
107             )
108 
109     override val selections: Flow<Map<String, List<String>>> = _selections
110 
111     override fun getSelections(): Map<String, List<String>> {
112         return _selections.value
113     }
114 
115     override fun setSelections(slotId: String, affordanceIds: List<String>) {
116         clientOrNull.value?.let { client ->
117             scope.launch {
118                 client.deleteAllSelections(slotId = slotId)
119                 affordanceIds.forEach { affordanceId ->
120                     client.insertSelection(slotId = slotId, affordanceId = affordanceId)
121                 }
122             }
123         }
124     }
125 
126     companion object {
127         private const val TAG = "KeyguardQuickAffordanceMultiUserSelectionManager"
128     }
129 }
130