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.Test 37 import org.junit.runner.RunWith 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 lateinit var jobService: AccessibilityJobService 57 private lateinit var context: Context 58 private lateinit var mockitoSession: MockitoSession 59 private lateinit var accessibilitySourceService: AccessibilitySourceService 60 private var shouldCancel = false 61 private lateinit var sharedPref: SharedPreferences 62 63 @Before setupnull64 fun setup() { 65 MockitoAnnotations.initMocks(this) 66 context = ApplicationProvider.getApplicationContext() 67 68 mockitoSession = 69 ExtendedMockito.mockitoSession() 70 .mockStatic(DeviceConfig::class.java) 71 .strictness(Strictness.LENIENT) 72 .startMocking() 73 74 accessibilitySourceService = runWithShellPermissionIdentity { 75 AccessibilitySourceService(context) 76 } 77 sharedPref = accessibilitySourceService.getSharedPreference() 78 sharedPref.edit().clear().apply() 79 } 80 81 @After cleanupnull82 fun cleanup() { 83 shouldCancel = false 84 mockitoSession.finishMocking() 85 sharedPref.edit().clear().apply() 86 } 87 88 @Test processAccessibilityJobWithCancellationnull89 fun processAccessibilityJobWithCancellation() { 90 shouldCancel = true 91 val jobParameters = mock(JobParameters::class.java) 92 93 runWithShellPermissionIdentity { 94 runBlocking { 95 accessibilitySourceService.processAccessibilityJob(jobParameters, jobService) { 96 shouldCancel 97 } 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 { accessibilitySourceService.markServiceAsNotified(a11yService) } 107 108 val storedServices = getNotifiedServices() 109 assertThat(storedServices.size).isEqualTo(1) 110 assertThat(storedServices.iterator().next()).isEqualTo(a11yService.flattenToShortString()) 111 } 112 113 @Test markAsNotifiedWithSecondComponentnull114 fun markAsNotifiedWithSecondComponent() { 115 val testComponent = ComponentName("com.test.package", "TestClass") 116 val testComponent2 = ComponentName("com.test.package2", "TestClass2") 117 118 var notifiedServices = runBlocking { 119 accessibilitySourceService.markServiceAsNotified(testComponent) 120 getNotifiedServices() 121 } 122 assertThat(notifiedServices.size).isEqualTo(1) 123 assertThat(notifiedServices.iterator().next()) 124 .isEqualTo(testComponent.flattenToShortString()) 125 126 notifiedServices = runBlocking { 127 accessibilitySourceService.markServiceAsNotified(testComponent2) 128 getNotifiedServices() 129 } 130 assertThat(notifiedServices.size).isEqualTo(2) 131 val expectedServices = listOf(testComponent, testComponent2) 132 expectedServices.forEach { 133 assertThat(notifiedServices.contains(it.flattenToShortString())).isTrue() 134 } 135 } 136 @Test removeNotifiedServicenull137 fun removeNotifiedService() { 138 val a11yService = ComponentName("com.test.package", "AccessibilityService") 139 val a11yService2 = ComponentName("com.test.package", "AccessibilityService2") 140 val a11yService3 = ComponentName("com.test.package", "AccessibilityService3") 141 val allServices = listOf(a11yService, a11yService2, a11yService3) 142 143 val notifiedServices = runBlocking { 144 allServices.forEach { accessibilitySourceService.markServiceAsNotified(it) } 145 accessibilitySourceService.removeFromNotifiedServices(a11yService2) 146 getNotifiedServices() 147 } 148 val expectedServices = listOf(a11yService, a11yService3) 149 assertThat(notifiedServices.size).isEqualTo(2) 150 expectedServices.forEach { 151 assertThat(notifiedServices.contains(it.flattenToShortString())).isTrue() 152 } 153 } 154 155 @Test removePackageStatenull156 fun removePackageState() { 157 val testComponent = ComponentName("com.test.package", "TestClass") 158 val testComponent2 = ComponentName("com.test.package", "TestClass2") 159 val testComponent3 = ComponentName("com.test.package2", "TestClass3") 160 val testComponents = listOf(testComponent, testComponent2, testComponent3) 161 162 val notifiedServices = runBlocking { 163 testComponents.forEach { accessibilitySourceService.markServiceAsNotified(it) } 164 accessibilitySourceService.removePackageState(testComponent.packageName) 165 getNotifiedServices() 166 } 167 168 assertThat(notifiedServices.size).isEqualTo(1) 169 assertThat(notifiedServices.contains(testComponent3.flattenToShortString())).isTrue() 170 } 171 172 @Test removePackageStateWithMultipleServicePerPackagenull173 fun removePackageStateWithMultipleServicePerPackage() { 174 val testComponent = ComponentName("com.test.package", "TestClass") 175 val testComponent2 = ComponentName("com.test.package", "TestClass2") 176 val testComponents = listOf(testComponent, testComponent2) 177 178 val notifiedServices = runBlocking { 179 testComponents.forEach { accessibilitySourceService.markServiceAsNotified(it) } 180 accessibilitySourceService.removePackageState(testComponent.packageName) 181 getNotifiedServices() 182 } 183 184 assertThat(notifiedServices).isEmpty() 185 } 186 187 @Test removePackageState_noPreviouslyNotifiedPackagenull188 fun removePackageState_noPreviouslyNotifiedPackage() { 189 val testComponent = ComponentName("com.test.package", "TestClass") 190 191 // Get the initial list of Components 192 val initialComponents = getNotifiedServices() 193 194 // Verify no components are present 195 assertThat(initialComponents).isEmpty() 196 197 // Forget about test package, and get the resulting list of Components 198 // Filter to the component that match the test component 199 val updatedComponents = runWithShellPermissionIdentity { 200 runBlocking { 201 // Verify this should not fail! 202 accessibilitySourceService.removePackageState(testComponent.packageName) 203 getNotifiedServices() 204 } 205 } 206 207 // Verify no components are present 208 assertThat(updatedComponents).isEmpty() 209 } 210 getNotifiedServicesnull211 private fun getNotifiedServices(): MutableSet<String> { 212 return sharedPref.getStringSet( 213 AccessibilitySourceService.KEY_ALREADY_NOTIFIED_SERVICES, 214 mutableSetOf<String>() 215 )!! 216 } 217 runWithShellPermissionIdentitynull218 private fun <R> runWithShellPermissionIdentity(block: () -> R): R { 219 val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() 220 uiAutomation.adoptShellPermissionIdentity() 221 try { 222 return block() 223 } finally { 224 uiAutomation.dropShellPermissionIdentity() 225 } 226 } 227 } 228