1 /*
2  * 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.repository
19 
20 import android.content.pm.UserInfo
21 import android.os.UserHandle
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.SmallTest
24 import com.android.systemui.R
25 import com.android.systemui.RoboPilotTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.coroutines.collectLastValue
28 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
29 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
30 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
31 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
32 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
33 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
34 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
35 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
36 import com.android.systemui.settings.FakeUserTracker
37 import com.android.systemui.settings.UserFileManager
38 import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient
39 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
40 import com.android.systemui.util.FakeSharedPreferences
41 import com.android.systemui.util.mockito.mock
42 import com.android.systemui.util.mockito.whenever
43 import com.android.systemui.util.settings.FakeSettings
44 import com.google.common.truth.Truth.assertThat
45 import java.util.Locale
46 import kotlinx.coroutines.ExperimentalCoroutinesApi
47 import kotlinx.coroutines.test.StandardTestDispatcher
48 import kotlinx.coroutines.test.TestScope
49 import kotlinx.coroutines.test.runTest
50 import org.junit.After
51 import org.junit.Before
52 import org.junit.Test
53 import org.junit.runner.RunWith
54 import org.mockito.ArgumentMatchers.anyInt
55 import org.mockito.ArgumentMatchers.anyString
56 
57 @OptIn(ExperimentalCoroutinesApi::class)
58 @SmallTest
59 @RoboPilotTest
60 @RunWith(AndroidJUnit4::class)
61 class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
62 
63     private lateinit var underTest: KeyguardQuickAffordanceRepository
64 
65     private lateinit var config1: FakeKeyguardQuickAffordanceConfig
66     private lateinit var config2: FakeKeyguardQuickAffordanceConfig
67     private lateinit var userTracker: FakeUserTracker
68     private lateinit var client1: FakeCustomizationProviderClient
69     private lateinit var client2: FakeCustomizationProviderClient
70     private lateinit var testScope: TestScope
71 
72     @Before
73     fun setUp() {
74         overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
75         context.resources.configuration.setLayoutDirection(Locale.US)
76         config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
77         config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
78         val testDispatcher = StandardTestDispatcher()
79         testScope = TestScope(testDispatcher)
80         userTracker = FakeUserTracker()
81         val localUserSelectionManager =
82             KeyguardQuickAffordanceLocalUserSelectionManager(
83                 context = context,
84                 userFileManager =
85                     mock<UserFileManager>().apply {
86                         whenever(
87                                 getSharedPreferences(
88                                     anyString(),
89                                     anyInt(),
90                                     anyInt(),
91                                 )
92                             )
93                             .thenReturn(FakeSharedPreferences())
94                     },
95                 userTracker = userTracker,
96                 broadcastDispatcher = fakeBroadcastDispatcher,
97             )
98         client1 = FakeCustomizationProviderClient()
99         client2 = FakeCustomizationProviderClient()
100         val remoteUserSelectionManager =
101             KeyguardQuickAffordanceRemoteUserSelectionManager(
102                 scope = testScope.backgroundScope,
103                 userTracker = userTracker,
104                 clientFactory =
105                     FakeKeyguardQuickAffordanceProviderClientFactory(
106                         userTracker,
107                     ) { selectedUserId ->
108                         when (selectedUserId) {
109                             SECONDARY_USER_1 -> client1
110                             SECONDARY_USER_2 -> client2
111                             else -> error("No set-up client for user $selectedUserId!")
112                         }
113                     },
114                 userHandle = UserHandle.SYSTEM,
115             )
116 
117         overrideResource(
118             R.array.config_keyguardQuickAffordanceDefaults,
119             arrayOf<String>(),
120         )
121 
122         underTest =
123             KeyguardQuickAffordanceRepository(
124                 appContext = context,
125                 scope = testScope.backgroundScope,
126                 localUserSelectionManager = localUserSelectionManager,
127                 remoteUserSelectionManager = remoteUserSelectionManager,
128                 userTracker = userTracker,
129                 legacySettingSyncer =
130                     KeyguardQuickAffordanceLegacySettingSyncer(
131                         scope = testScope.backgroundScope,
132                         backgroundDispatcher = testDispatcher,
133                         secureSettings = FakeSettings(),
134                         selectionsManager = localUserSelectionManager,
135                     ),
136                 configs = setOf(config1, config2),
137                 dumpManager = mock(),
138                 userHandle = UserHandle.SYSTEM,
139             )
140     }
141 
142     @After
143     fun tearDown() {
144         mContext
145             .getOrCreateTestableResources()
146             .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled)
147     }
148 
149     @Test
150     fun setSelections() =
151         testScope.runTest {
152             val configsBySlotId = collectLastValue(underTest.selections)
153             val slotId1 = "slot1"
154             val slotId2 = "slot2"
155 
156             underTest.setSelections(slotId1, listOf(config1.key))
157             assertSelections(
158                 configsBySlotId(),
159                 mapOf(
160                     slotId1 to listOf(config1),
161                 ),
162             )
163 
164             underTest.setSelections(slotId2, listOf(config2.key))
165             assertSelections(
166                 configsBySlotId(),
167                 mapOf(
168                     slotId1 to listOf(config1),
169                     slotId2 to listOf(config2),
170                 ),
171             )
172 
173             underTest.setSelections(slotId1, emptyList())
174             underTest.setSelections(slotId2, listOf(config1.key))
175             assertSelections(
176                 configsBySlotId(),
177                 mapOf(
178                     slotId1 to emptyList(),
179                     slotId2 to listOf(config1),
180                 ),
181             )
182         }
183 
184     @Test
185     fun getAffordancePickerRepresentations() =
186         testScope.runTest {
187             assertThat(underTest.getAffordancePickerRepresentations())
188                 .isEqualTo(
189                     listOf(
190                         KeyguardQuickAffordancePickerRepresentation(
191                             id = config1.key,
192                             name = config1.pickerName(),
193                             iconResourceId = config1.pickerIconResourceId,
194                         ),
195                         KeyguardQuickAffordancePickerRepresentation(
196                             id = config2.key,
197                             name = config2.pickerName(),
198                             iconResourceId = config2.pickerIconResourceId,
199                         ),
200                     )
201                 )
202         }
203 
204     @Test
205     fun getSlotPickerRepresentations() {
206         val slot1 = "slot1"
207         val slot2 = "slot2"
208         val slot3 = "slot3"
209         context.orCreateTestableResources.addOverride(
210             R.array.config_keyguardQuickAffordanceSlots,
211             arrayOf(
212                 "$slot1:2",
213                 "$slot2:4",
214                 "$slot3:5",
215             ),
216         )
217 
218         assertThat(underTest.getSlotPickerRepresentations())
219             .isEqualTo(
220                 listOf(
221                     KeyguardSlotPickerRepresentation(
222                         id = slot1,
223                         maxSelectedAffordances = 2,
224                     ),
225                     KeyguardSlotPickerRepresentation(
226                         id = slot2,
227                         maxSelectedAffordances = 4,
228                     ),
229                     KeyguardSlotPickerRepresentation(
230                         id = slot3,
231                         maxSelectedAffordances = 5,
232                     ),
233                 )
234             )
235     }
236 
237     @Test
238     fun getSlotPickerRepresentations_rightToLeft_slotsReversed() {
239         context.resources.configuration.setLayoutDirection(Locale("he", "IL"))
240         val slot1 = "slot1"
241         val slot2 = "slot2"
242         val slot3 = "slot3"
243         context.orCreateTestableResources.addOverride(
244             R.array.config_keyguardQuickAffordanceSlots,
245             arrayOf(
246                 "$slot1:2",
247                 "$slot2:4",
248                 "$slot3:5",
249             ),
250         )
251 
252         assertThat(underTest.getSlotPickerRepresentations())
253             .isEqualTo(
254                 listOf(
255                     KeyguardSlotPickerRepresentation(
256                         id = slot3,
257                         maxSelectedAffordances = 5,
258                     ),
259                     KeyguardSlotPickerRepresentation(
260                         id = slot2,
261                         maxSelectedAffordances = 4,
262                     ),
263                     KeyguardSlotPickerRepresentation(
264                         id = slot1,
265                         maxSelectedAffordances = 2,
266                     ),
267                 )
268             )
269     }
270 
271     @Test
272     fun selectionsForSecondaryUser() =
273         testScope.runTest {
274             userTracker.set(
275                 userInfos =
276                     listOf(
277                         UserInfo(
278                             UserHandle.USER_SYSTEM,
279                             "Primary",
280                             /* flags= */ 0,
281                         ),
282                         UserInfo(
283                             SECONDARY_USER_1,
284                             "Secondary 1",
285                             /* flags= */ 0,
286                         ),
287                         UserInfo(
288                             SECONDARY_USER_2,
289                             "Secondary 2",
290                             /* flags= */ 0,
291                         ),
292                     ),
293                 selectedUserIndex = 2,
294             )
295             client2.insertSelection(
296                 slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
297                 affordanceId = FakeCustomizationProviderClient.AFFORDANCE_2,
298             )
299             val observed = collectLastValue(underTest.selections)
300 
301             assertSelections(
302                 observed = observed(),
303                 expected =
304                     mapOf(
305                         KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
306                             listOf(
307                                 config2,
308                             ),
309                     )
310             )
311         }
312 
313     private fun assertSelections(
314         observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
315         expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
316     ) {
317         assertThat(observed).isEqualTo(expected)
318         assertThat(underTest.getCurrentSelections())
319             .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
320         expected.forEach { (slotId, configs) ->
321             assertThat(underTest.getCurrentSelections(slotId)).isEqualTo(configs)
322         }
323     }
324 
325     companion object {
326         private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
327         private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
328     }
329 }
330