1 /*
2  * Copyright (C) 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.systemui.accessibility.data.repository
18 
19 import android.accessibilityservice.AccessibilityServiceInfo
20 import android.content.ComponentName
21 import android.content.pm.ResolveInfo
22 import android.content.pm.ServiceInfo
23 import android.view.accessibility.AccessibilityManager
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.internal.accessibility.AccessibilityShortcutController
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.qs.pipeline.shared.TileSpec
29 import com.android.systemui.qs.tiles.ColorCorrectionTile
30 import com.android.systemui.qs.tiles.ColorInversionTile
31 import com.android.systemui.qs.tiles.FontScalingTile
32 import com.android.systemui.qs.tiles.HearingDevicesTile
33 import com.android.systemui.qs.tiles.OneHandedModeTile
34 import com.android.systemui.qs.tiles.ReduceBrightColorsTile
35 import com.android.systemui.util.mockito.whenever
36 import com.android.systemui.util.settings.FakeSettings
37 import com.google.common.truth.Truth.assertThat
38 import kotlinx.coroutines.ExperimentalCoroutinesApi
39 import kotlinx.coroutines.test.StandardTestDispatcher
40 import kotlinx.coroutines.test.TestScope
41 import kotlinx.coroutines.test.runCurrent
42 import kotlinx.coroutines.test.runTest
43 import org.junit.Before
44 import org.junit.Rule
45 import org.junit.Test
46 import org.junit.runner.RunWith
47 import org.mockito.Mock
48 import org.mockito.Mockito
49 import org.mockito.internal.util.reflection.FieldSetter
50 import org.mockito.junit.MockitoJUnit
51 import org.mockito.junit.MockitoRule
52 
53 /**
54  * Unit tests for AccessibilityQsShortcutsRepositoryImpl that requires a device. For example, we
55  * can't mock the AccessibilityShortcutInfo for test. MultiValentTest doesn't compile when using
56  * newly introduced methods and constants.
57  */
58 @OptIn(ExperimentalCoroutinesApi::class)
59 @SmallTest
60 @RunWith(AndroidJUnit4::class)
61 class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() {
62     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
63 
64     // mocks
65     @Mock private lateinit var a11yManager: AccessibilityManager
66     private val testDispatcher = StandardTestDispatcher()
67     private val testScope = TestScope(testDispatcher)
68     private val secureSettings = FakeSettings()
69 
70     private val userA11yQsShortcutsRepositoryFactory =
71         object : UserA11yQsShortcutsRepository.Factory {
createnull72             override fun create(userId: Int): UserA11yQsShortcutsRepository {
73                 return UserA11yQsShortcutsRepository(
74                     userId,
75                     secureSettings,
76                     testScope.backgroundScope,
77                     testDispatcher,
78                 )
79             }
80         }
81 
82     private lateinit var underTest: AccessibilityQsShortcutsRepositoryImpl
83 
84     @Before
setUpnull85     fun setUp() {
86         underTest =
87             AccessibilityQsShortcutsRepositoryImpl(
88                 a11yManager,
89                 userA11yQsShortcutsRepositoryFactory,
90                 testDispatcher
91             )
92     }
93 
94     @Test
testTileSpecToComponentMappingContentnull95     fun testTileSpecToComponentMappingContent() {
96         val mapping = AccessibilityQsShortcutsRepositoryImpl.TILE_SPEC_TO_COMPONENT_MAPPING
97 
98         assertThat(mapping.size).isEqualTo(6)
99         assertThat(mapping[ColorCorrectionTile.TILE_SPEC])
100             .isEqualTo(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)
101         assertThat(mapping[ColorInversionTile.TILE_SPEC])
102             .isEqualTo(AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME)
103         assertThat(mapping[OneHandedModeTile.TILE_SPEC])
104             .isEqualTo(AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME)
105         assertThat(mapping[ReduceBrightColorsTile.TILE_SPEC])
106             .isEqualTo(
107                 AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME
108             )
109         assertThat(mapping[FontScalingTile.TILE_SPEC])
110             .isEqualTo(AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME)
111         assertThat(mapping[HearingDevicesTile.TILE_SPEC])
112             .isEqualTo(
113                 AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME
114             )
115     }
116 
117     @Test
notifyAccessibilityManagerTilesChanged_customTiles_onlyNotifyA11yTileServicesnull118     fun notifyAccessibilityManagerTilesChanged_customTiles_onlyNotifyA11yTileServices() =
119         testScope.runTest {
120             val a11yServiceTileService = ComponentName("a11yPackageName", "TileServiceClassName")
121             setupInstalledAccessibilityServices(a11yServiceTileService)
122             // TileService should match accessibility_shortcut_test_activity.xml,
123             // because this test uses the real installed activity list
124             val a11yShortcutTileService =
125                 ComponentName(
126                     mContext.packageName,
127                     "com.android.systemui.accessibility.TileService"
128                 )
129             setupInstalledAccessibilityShortcutTargets()
130             // Other custom tile service that isn't linked to an accessibility feature
131             val nonA11yTileService = ComponentName("C", "c")
132 
133             val changedTiles =
134                 listOf(
135                     TileSpec.create(a11yServiceTileService),
136                     TileSpec.create(a11yShortcutTileService),
137                     TileSpec.create(nonA11yTileService)
138                 )
139 
140             underTest.notifyAccessibilityManagerTilesChanged(context, changedTiles)
141             runCurrent()
142 
143             Mockito.verify(a11yManager, Mockito.times(1))
144                 .notifyQuickSettingsTilesChanged(
145                     context.userId,
146                     listOf(a11yServiceTileService, a11yShortcutTileService)
147                 )
148         }
149 
150     @Test
notifyAccessibilityManagerTilesChanged_noMatchingA11yFrameworkTilesnull151     fun notifyAccessibilityManagerTilesChanged_noMatchingA11yFrameworkTiles() =
152         testScope.runTest {
153             val changedTiles = listOf(TileSpec.create("a"))
154 
155             underTest.notifyAccessibilityManagerTilesChanged(context, changedTiles)
156             runCurrent()
157 
158             Mockito.verify(a11yManager, Mockito.times(1))
159                 .notifyQuickSettingsTilesChanged(context.userId, emptyList())
160         }
161 
162     @Test
notifyAccessibilityManagerTilesChanged_convertA11yTilesSpecToComponentNamenull163     fun notifyAccessibilityManagerTilesChanged_convertA11yTilesSpecToComponentName() =
164         testScope.runTest {
165             val changedTiles =
166                 listOf(
167                     TileSpec.create(ColorCorrectionTile.TILE_SPEC),
168                     TileSpec.create(ColorInversionTile.TILE_SPEC),
169                     TileSpec.create(OneHandedModeTile.TILE_SPEC),
170                     TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
171                     TileSpec.create(FontScalingTile.TILE_SPEC)
172                 )
173 
174             underTest.notifyAccessibilityManagerTilesChanged(context, changedTiles)
175             runCurrent()
176 
177             Mockito.verify(a11yManager, Mockito.times(1))
178                 .notifyQuickSettingsTilesChanged(
179                     context.userId,
180                     listOf(
181                         AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
182                         AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME,
183                         AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME,
184                         AccessibilityShortcutController
185                             .REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME,
186                         AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME
187                     )
188                 )
189         }
190 
setupInstalledAccessibilityShortcutTargetsnull191     private fun setupInstalledAccessibilityShortcutTargets() {
192         // Can't create a mock AccessibilityShortcutInfo because it's final.
193         // Use the real AccessibilityManager to get the AccessibilityShortcutInfo
194         val realA11yManager = context.getSystemService(AccessibilityManager::class.java)!!
195         val installedA11yActivities =
196             realA11yManager.getInstalledAccessibilityShortcutListAsUser(context, context.userId)
197 
198         whenever(a11yManager.getInstalledAccessibilityShortcutListAsUser(context, context.userId))
199             .thenReturn(installedA11yActivities)
200     }
201 
setupInstalledAccessibilityServicesnull202     private fun setupInstalledAccessibilityServices(tileService: ComponentName) {
203         whenever(a11yManager.installedAccessibilityServiceList)
204             .thenReturn(
205                 listOf(
206                     createFakeAccessibilityServiceInfo(
207                         tileService.packageName,
208                         tileService.className
209                     )
210                 )
211             )
212     }
213 
createFakeAccessibilityServiceInfonull214     private fun createFakeAccessibilityServiceInfo(
215         packageName: String,
216         tileServiceClass: String
217     ): AccessibilityServiceInfo {
218         val serviceInfo = ServiceInfo().also { it.packageName = packageName }
219         val resolveInfo = ResolveInfo().also { it.serviceInfo = serviceInfo }
220 
221         val a11yServiceInfo = AccessibilityServiceInfo().also { it.resolveInfo = resolveInfo }
222 
223         // Somehow unable to mock the a11yServiceInfo.tileServiceName
224         // Use reflection instead.
225         FieldSetter.setField(
226             a11yServiceInfo,
227             AccessibilityServiceInfo::class.java.getDeclaredField("mTileServiceName"),
228             tileServiceClass
229         )
230 
231         return a11yServiceInfo
232     }
233 }
234