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 package com.android.systemui.controls.management
18 
19 import android.content.ComponentName
20 import android.content.res.Resources
21 import android.testing.AndroidTestingRunner
22 import android.testing.TestableLooper
23 import android.view.LayoutInflater
24 import android.view.View
25 import androidx.test.filters.SmallTest
26 import com.android.settingslib.core.lifecycle.Lifecycle
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.controls.ControlsServiceInfo
29 import com.android.systemui.util.concurrency.FakeExecutor
30 import com.android.systemui.util.mockito.any
31 import com.android.systemui.util.mockito.argumentCaptor
32 import com.android.systemui.util.mockito.capture
33 import com.android.systemui.util.mockito.mock
34 import com.android.systemui.util.time.FakeSystemClock
35 import com.google.common.truth.Truth.assertThat
36 import org.junit.Before
37 import org.junit.Test
38 import org.junit.runner.RunWith
39 import org.mockito.ArgumentCaptor
40 import org.mockito.Mock
41 import org.mockito.Mockito.`when`
42 import org.mockito.Mockito.clearInvocations
43 import org.mockito.Mockito.verify
44 import org.mockito.MockitoAnnotations
45 import java.text.Collator
46 
47 @SmallTest
48 @RunWith(AndroidTestingRunner::class)
49 @TestableLooper.RunWithLooper(setAsMainLooper = true)
50 class AppAdapterTest : SysuiTestCase() {
51     private val fakeSystemClock = FakeSystemClock()
52     private val backgroundExecutor = FakeExecutor(fakeSystemClock)
53     private val uiExecutor = FakeExecutor(fakeSystemClock)
54     @Mock lateinit var lifecycle: Lifecycle
55     @Mock lateinit var controlsListingController: ControlsListingController
56     @Mock lateinit var layoutInflater: LayoutInflater
57     @Mock lateinit var onAppSelected: (ControlsServiceInfo) -> Unit
58     @Mock lateinit var favoritesRenderer: FavoritesRenderer
59     val resources: Resources = context.resources
60     lateinit var adapter: AppAdapter
61     @Before
62     fun setUp() {
63         MockitoAnnotations.initMocks(this)
64     }
65 
66     @Test
67     fun testOnServicesUpdated_nullLoadLabel() {
68         adapter = createAdapterWithAuthorizedPanels(emptySet())
69         val captor = ArgumentCaptor
70             .forClass(ControlsListingController.ControlsListingCallback::class.java)
71         val controlsServiceInfo = mock<ControlsServiceInfo>()
72         val serviceInfo = listOf(controlsServiceInfo)
73         `when`(controlsServiceInfo.loadLabel()).thenReturn(null)
74         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
75 
76         captor.value.onServicesUpdated(serviceInfo)
77         FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
78 
79         assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
80     }
81 
82     @Test
83     fun testOnServicesUpdated_showsNotAuthorizedPanels() {
84         adapter = createAdapterWithAuthorizedPanels(emptySet())
85         val captor = ArgumentCaptor
86                 .forClass(ControlsListingController.ControlsListingCallback::class.java)
87         val serviceInfo = listOf(
88                 ControlsServiceInfo("no panel", null),
89                 ControlsServiceInfo("panel", mock())
90         )
91         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
92 
93         captor.value.onServicesUpdated(serviceInfo)
94         FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
95 
96         assertThat(adapter.itemCount).isEqualTo(2)
97     }
98 
99     @Test
100     fun testOnServicesUpdated_doesntShowAuthorizedPanels() {
101         adapter = createAdapterWithAuthorizedPanels(setOf(TEST_PACKAGE))
102 
103         val captor = ArgumentCaptor
104                 .forClass(ControlsListingController.ControlsListingCallback::class.java)
105         val serviceInfo = listOf(
106                 ControlsServiceInfo("no panel", null),
107                 ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls"))
108         )
109         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
110 
111         captor.value.onServicesUpdated(serviceInfo)
112         FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
113 
114         assertThat(adapter.itemCount).isEqualTo(1)
115     }
116 
117     @Test
118     fun testOnBindSetsClickListenerToCallOnAppSelected() {
119         adapter = createAdapterWithAuthorizedPanels(emptySet())
120 
121         val captor = ArgumentCaptor
122                 .forClass(ControlsListingController.ControlsListingCallback::class.java)
123         val serviceInfo = listOf(
124                 ControlsServiceInfo("no panel", null),
125                 ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls"))
126         )
127         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
128 
129         captor.value.onServicesUpdated(serviceInfo)
130         FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
131 
132         val sorted = serviceInfo.sortedWith(
133                 compareBy(Collator.getInstance(resources.configuration.locales[0])) {
134                     it.loadLabel() ?: ""
135                 })
136 
137         sorted.forEachIndexed { index, info ->
138             val fakeView: View = mock()
139             val fakeHolder: AppAdapter.Holder = mock()
140             `when`(fakeHolder.view).thenReturn(fakeView)
141 
142             clearInvocations(onAppSelected)
143             adapter.onBindViewHolder(fakeHolder, index)
144             val listenerCaptor: ArgumentCaptor<View.OnClickListener> = argumentCaptor()
145             verify(fakeView).setOnClickListener(capture(listenerCaptor))
146             listenerCaptor.value.onClick(fakeView)
147 
148             verify(onAppSelected).invoke(info)
149         }
150     }
151 
152     private fun createAdapterWithAuthorizedPanels(packages: Set<String>): AppAdapter {
153         return AppAdapter(backgroundExecutor,
154                 uiExecutor,
155                 lifecycle,
156                 controlsListingController,
157                 layoutInflater,
158                 onAppSelected,
159                 favoritesRenderer,
160                 resources,
161                 packages)
162     }
163 
164     companion object {
165         private fun ControlsServiceInfo(
166                 label: CharSequence,
167                 panelComponentName: ComponentName? = null
168         ): ControlsServiceInfo {
169             return mock {
170                 `when`(loadLabel()).thenReturn(label)
171                 `when`(panelActivity).thenReturn(panelComponentName)
172                 `when`(loadIcon()).thenReturn(mock())
173             }
174         }
175 
176         private const val TEST_PACKAGE = "package"
177     }
178 }
179