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.permissioncontroller.tests.mocking.privacysources 18 19 import android.app.job.JobParameters 20 import android.content.ComponentName 21 import android.content.Context 22 import android.content.SharedPreferences 23 import android.os.Build 24 import android.provider.DeviceConfig 25 import androidx.test.core.app.ApplicationProvider 26 import androidx.test.ext.junit.runners.AndroidJUnit4 27 import androidx.test.filters.SdkSuppress 28 import androidx.test.platform.app.InstrumentationRegistry 29 import com.android.dx.mockito.inline.extended.ExtendedMockito 30 import com.android.permissioncontroller.privacysources.AccessibilityJobService 31 import com.android.permissioncontroller.privacysources.AccessibilitySourceService 32 import com.google.common.truth.Truth.assertThat 33 import kotlinx.coroutines.runBlocking 34 import org.junit.After 35 import org.junit.Before 36 import org.junit.runner.RunWith 37 import org.junit.Test 38 import org.mockito.Mock 39 import org.mockito.Mockito.mock 40 import org.mockito.Mockito.verify 41 import org.mockito.MockitoAnnotations 42 import org.mockito.MockitoSession 43 import org.mockito.quality.Strictness 44 45 /** 46 * Unit tests for internal [AccessibilitySourceService] 47 * 48 * <p> Does not test notification as there are conflicts with being able to mock NotificationManager 49 * and PendingIntent.getBroadcast requiring a valid context. Notifications are tested in the CTS 50 * integration tests 51 */ 52 @RunWith(AndroidJUnit4::class) 53 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") 54 class AccessibilitySourceServiceTest { 55 56 @Mock 57 lateinit var jobService: AccessibilityJobService 58 private lateinit var context: Context 59 private lateinit var mockitoSession: MockitoSession 60 private lateinit var accessibilitySourceService: AccessibilitySourceService 61 private var shouldCancel = false 62 private lateinit var sharedPref: SharedPreferences 63 64 @Before setupnull65 fun setup() { 66 MockitoAnnotations.initMocks(this) 67 context = ApplicationProvider.getApplicationContext() 68 69 mockitoSession = ExtendedMockito.mockitoSession() 70 .mockStatic(DeviceConfig::class.java) 71 .strictness(Strictness.LENIENT).startMocking() 72 73 accessibilitySourceService = runWithShellPermissionIdentity { 74 AccessibilitySourceService(context) 75 } 76 sharedPref = accessibilitySourceService.getSharedPreference() 77 sharedPref.edit().clear().apply() 78 } 79 80 @After cleanupnull81 fun cleanup() { 82 shouldCancel = false 83 mockitoSession.finishMocking() 84 sharedPref.edit().clear().apply() 85 } 86 87 @Test processAccessibilityJobWithCancellationnull88 fun processAccessibilityJobWithCancellation() { 89 shouldCancel = true 90 val jobParameters = mock(JobParameters::class.java) 91 92 runWithShellPermissionIdentity { 93 runBlocking { 94 accessibilitySourceService.processAccessibilityJob( 95 jobParameters, 96 jobService 97 ) { shouldCancel } 98 } 99 } 100 verify(jobService).jobFinished(jobParameters, true) 101 } 102 103 @Test markServiceAsNotifiednull104 fun markServiceAsNotified() { 105 val a11yService = ComponentName("com.test.package", "AccessibilityService") 106 runBlocking { 107 accessibilitySourceService.markServiceAsNotified(a11yService) 108 } 109 110 val storedServices = getNotifiedServices() 111 assertThat(storedServices.size).isEqualTo(1) 112 assertThat(storedServices.iterator().next()).isEqualTo(a11yService.flattenToShortString()) 113 } 114 115 @Test markAsNotifiedWithSecondComponentnull116 fun markAsNotifiedWithSecondComponent() { 117 val testComponent = ComponentName("com.test.package", "TestClass") 118 val testComponent2 = ComponentName("com.test.package2", "TestClass2") 119 120 var notifiedServices = runBlocking { 121 accessibilitySourceService.markServiceAsNotified(testComponent) 122 getNotifiedServices() 123 } 124 assertThat(notifiedServices.size).isEqualTo(1) 125 assertThat(notifiedServices.iterator().next()) 126 .isEqualTo(testComponent.flattenToShortString()) 127 128 notifiedServices = runBlocking { 129 accessibilitySourceService.markServiceAsNotified(testComponent2) 130 getNotifiedServices() 131 } 132 assertThat(notifiedServices.size).isEqualTo(2) 133 val expectedServices = listOf(testComponent, testComponent2) 134 expectedServices.forEach { 135 assertThat(notifiedServices.contains(it.flattenToShortString())).isTrue() 136 } 137 } 138 @Test removeNotifiedServicenull139 fun removeNotifiedService() { 140 val a11yService = ComponentName("com.test.package", "AccessibilityService") 141 val a11yService2 = ComponentName("com.test.package", "AccessibilityService2") 142 val a11yService3 = ComponentName("com.test.package", "AccessibilityService3") 143 val allServices = listOf(a11yService, a11yService2, a11yService3) 144 145 val notifiedServices = runBlocking { 146 allServices.forEach { 147 accessibilitySourceService.markServiceAsNotified(it) 148 } 149 accessibilitySourceService.removeFromNotifiedServices(a11yService2) 150 getNotifiedServices() 151 } 152 val expectedServices = listOf(a11yService, a11yService3) 153 assertThat(notifiedServices.size).isEqualTo(2) 154 expectedServices.forEach { 155 assertThat(notifiedServices.contains(it.flattenToShortString())).isTrue() 156 } 157 } 158 159 @Test removePackageStatenull160 fun removePackageState() { 161 val testComponent = ComponentName("com.test.package", "TestClass") 162 val testComponent2 = ComponentName("com.test.package", "TestClass2") 163 val testComponent3 = ComponentName("com.test.package2", "TestClass3") 164 val testComponents = listOf(testComponent, testComponent2, testComponent3) 165 166 val notifiedServices = runBlocking { 167 testComponents.forEach { 168 accessibilitySourceService.markServiceAsNotified(it) 169 } 170 accessibilitySourceService.removePackageState(testComponent.packageName) 171 getNotifiedServices() 172 } 173 174 assertThat(notifiedServices.size).isEqualTo(1) 175 assertThat(notifiedServices.contains(testComponent3.flattenToShortString())).isTrue() 176 } 177 178 @Test removePackageStateWithMultipleServicePerPackagenull179 fun removePackageStateWithMultipleServicePerPackage() { 180 val testComponent = ComponentName("com.test.package", "TestClass") 181 val testComponent2 = ComponentName("com.test.package", "TestClass2") 182 val testComponents = listOf(testComponent, testComponent2) 183 184 val notifiedServices = runBlocking { 185 testComponents.forEach { 186 accessibilitySourceService.markServiceAsNotified(it) 187 } 188 accessibilitySourceService.removePackageState(testComponent.packageName) 189 getNotifiedServices() 190 } 191 192 assertThat(notifiedServices).isEmpty() 193 } 194 195 @Test removePackageState_noPreviouslyNotifiedPackagenull196 fun removePackageState_noPreviouslyNotifiedPackage() { 197 val testComponent = ComponentName("com.test.package", "TestClass") 198 199 // Get the initial list of Components 200 val initialComponents = getNotifiedServices() 201 202 // Verify no components are present 203 assertThat(initialComponents).isEmpty() 204 205 // Forget about test package, and get the resulting list of Components 206 // Filter to the component that match the test component 207 val updatedComponents = runWithShellPermissionIdentity { 208 runBlocking { 209 // Verify this should not fail! 210 accessibilitySourceService.removePackageState(testComponent.packageName) 211 getNotifiedServices() 212 } 213 } 214 215 // Verify no components are present 216 assertThat(updatedComponents).isEmpty() 217 } 218 getNotifiedServicesnull219 private fun getNotifiedServices(): MutableSet<String> { 220 return sharedPref.getStringSet( 221 AccessibilitySourceService.KEY_ALREADY_NOTIFIED_SERVICES, 222 mutableSetOf<String>() 223 )!! 224 } 225 runWithShellPermissionIdentitynull226 private fun <R> runWithShellPermissionIdentity(block: () -> R): R { 227 val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() 228 uiAutomation.adoptShellPermissionIdentity() 229 try { 230 return block() 231 } finally { 232 uiAutomation.dropShellPermissionIdentity() 233 } 234 } 235 } 236