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