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