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.settingslib.spaprivileged.template.app
18 
19 import android.app.AppOpsManager
20 import android.content.Context
21 import android.content.pm.ApplicationInfo
22 import android.content.pm.PackageManager
23 import androidx.compose.runtime.State
24 import androidx.compose.ui.test.junit4.createComposeRule
25 import androidx.lifecycle.MutableLiveData
26 import androidx.test.core.app.ApplicationProvider
27 import androidx.test.ext.junit.runners.AndroidJUnit4
28 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
29 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
30 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
31 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
32 import com.android.settingslib.spaprivileged.test.R
33 import com.google.common.truth.Truth.assertThat
34 import kotlinx.coroutines.ExperimentalCoroutinesApi
35 import kotlinx.coroutines.flow.flowOf
36 import kotlinx.coroutines.test.runTest
37 import org.junit.Before
38 import org.junit.Rule
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 import org.mockito.Mock
42 import org.mockito.Mockito.any
43 import org.mockito.Mockito.anyInt
44 import org.mockito.Mockito.anyString
45 import org.mockito.Mockito.doNothing
46 import org.mockito.Mockito.verify
47 import org.mockito.Mockito.`when` as whenever
48 import org.mockito.Spy
49 import org.mockito.junit.MockitoJUnit
50 import org.mockito.junit.MockitoRule
51 
52 @OptIn(ExperimentalCoroutinesApi::class)
53 @RunWith(AndroidJUnit4::class)
54 class AppOpPermissionAppListTest {
55     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
56 
57     @get:Rule val composeTestRule = createComposeRule()
58 
59     @Spy private val context: Context = ApplicationProvider.getApplicationContext()
60 
61     @Mock private lateinit var packageManagers: IPackageManagers
62 
63     @Mock private lateinit var appOpsManager: AppOpsManager
64 
65     @Mock private lateinit var packageManager: PackageManager
66 
67     private lateinit var listModel: TestAppOpPermissionAppListModel
68 
69     @Before
70     fun setUp() {
71         whenever(context.appOpsManager).thenReturn(appOpsManager)
72         whenever(context.packageManager).thenReturn(packageManager)
73         doNothing().`when`(packageManager)
74                 .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
75         listModel = TestAppOpPermissionAppListModel()
76     }
77 
78     @Test
79     fun transformItem_recordHasCorrectApp() {
80         val record = listModel.transformItem(APP)
81 
82         assertThat(record.app).isSameInstanceAs(APP)
83     }
84 
85     @Test
86     fun transformItem_hasRequestPermission() = runTest {
87         with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true) }
88 
89         val record = listModel.transformItem(APP)
90 
91         assertThat(record.hasRequestPermission).isTrue()
92     }
93 
94     @Test
95     fun transformItem_notRequestPermission() = runTest {
96         with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) }
97 
98         val record = listModel.transformItem(APP)
99 
100         assertThat(record.hasRequestPermission).isFalse()
101     }
102 
103     @Test
104     fun transformItem_hasRequestBroaderPermission() = runTest {
105         listModel.broaderPermission = BROADER_PERMISSION
106         with(packageManagers) {
107             whenever(APP.hasRequestPermission(BROADER_PERMISSION)).thenReturn(true)
108         }
109 
110         val record = listModel.transformItem(APP)
111 
112         assertThat(record.hasRequestBroaderPermission).isTrue()
113     }
114 
115     @Test
116     fun transformItem_notRequestBroaderPermission() = runTest {
117         listModel.broaderPermission = BROADER_PERMISSION
118         with(packageManagers) {
119             whenever(APP.hasRequestPermission(BROADER_PERMISSION)).thenReturn(false)
120         }
121 
122         val record = listModel.transformItem(APP)
123 
124         assertThat(record.hasRequestPermission).isFalse()
125     }
126 
127     @Test
128     fun filter() = runTest {
129         with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) }
130         val record =
131             AppOpPermissionRecord(
132                 app = APP,
133                 hasRequestBroaderPermission = false,
134                 hasRequestPermission = false,
135                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
136             )
137 
138         val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
139 
140         val recordList = recordListFlow.firstWithTimeoutOrNull()!!
141         assertThat(recordList).isEmpty()
142     }
143 
144     @Test
145     fun isAllowed_allowed() {
146         val record =
147             AppOpPermissionRecord(
148                 app = APP,
149                 hasRequestBroaderPermission = false,
150                 hasRequestPermission = true,
151                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
152             )
153 
154         val isAllowed = getIsAllowed(record)
155 
156         assertThat(isAllowed).isTrue()
157     }
158 
159     @Test
160     fun isAllowed_defaultAndHasGrantPermission() {
161         with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) }
162         val record =
163             AppOpPermissionRecord(
164                 app = APP,
165                 hasRequestBroaderPermission = false,
166                 hasRequestPermission = true,
167                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
168             )
169 
170         val isAllowed = getIsAllowed(record)
171 
172         assertThat(isAllowed).isTrue()
173     }
174 
175     @Test
176     fun isAllowed_defaultAndNotGrantPermission() {
177         with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) }
178         val record =
179             AppOpPermissionRecord(
180                 app = APP,
181                 hasRequestBroaderPermission = false,
182                 hasRequestPermission = true,
183                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
184             )
185 
186         val isAllowed = getIsAllowed(record)
187 
188         assertThat(isAllowed).isFalse()
189     }
190 
191     @Test
192     fun isAllowed_broaderPermissionTrumps() {
193         listModel.broaderPermission = BROADER_PERMISSION
194         with(packageManagers) {
195             whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
196             whenever(APP.hasGrantPermission(BROADER_PERMISSION)).thenReturn(true)
197         }
198         val record =
199             AppOpPermissionRecord(
200                 app = APP,
201                 hasRequestBroaderPermission = true,
202                 hasRequestPermission = false,
203                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
204             )
205 
206         val isAllowed = getIsAllowed(record)
207 
208         assertThat(isAllowed).isTrue()
209     }
210 
211     @Test
212     fun isAllowed_notAllowed() {
213         val record =
214             AppOpPermissionRecord(
215                 app = APP,
216                 hasRequestBroaderPermission = false,
217                 hasRequestPermission = true,
218                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
219             )
220 
221         val isAllowed = getIsAllowed(record)
222 
223         assertThat(isAllowed).isFalse()
224     }
225 
226     @Test
227     fun isChangeable_notRequestPermission() {
228         val record =
229             AppOpPermissionRecord(
230                 app = APP,
231                 hasRequestBroaderPermission = false,
232                 hasRequestPermission = false,
233                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
234             )
235 
236         val isChangeable = listModel.isChangeable(record)
237 
238         assertThat(isChangeable).isFalse()
239     }
240 
241     @Test
242     fun isChangeable_notChangeablePackages() {
243         val record =
244             AppOpPermissionRecord(
245                 app = NOT_CHANGEABLE_APP,
246                 hasRequestBroaderPermission = false,
247                 hasRequestPermission = true,
248                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
249             )
250 
251         val isChangeable = listModel.isChangeable(record)
252 
253         assertThat(isChangeable).isFalse()
254     }
255 
256     @Test
257     fun isChangeable_hasRequestPermissionAndChangeable() {
258         val record =
259             AppOpPermissionRecord(
260                 app = APP,
261                 hasRequestBroaderPermission = false,
262                 hasRequestPermission = true,
263                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
264             )
265 
266         val isChangeable = listModel.isChangeable(record)
267 
268         assertThat(isChangeable).isTrue()
269     }
270 
271     @Test
272     fun isChangeable_broaderPermissionTrumps() {
273         listModel.broaderPermission = BROADER_PERMISSION
274         val record =
275             AppOpPermissionRecord(
276                 app = APP,
277                 hasRequestBroaderPermission = true,
278                 hasRequestPermission = true,
279                 appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
280             )
281 
282         val isChangeable = listModel.isChangeable(record)
283 
284         assertThat(isChangeable).isFalse()
285     }
286 
287     @Test
288     fun setAllowed() {
289         val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
290         val record =
291             AppOpPermissionRecord(
292                 app = APP,
293                 hasRequestBroaderPermission = false,
294                 hasRequestPermission = true,
295                 appOpsController = appOpsController,
296             )
297 
298         listModel.setAllowed(record = record, newAllowed = true)
299 
300         assertThat(appOpsController.setAllowedCalledWith).isTrue()
301     }
302 
303     @Test
304     fun setAllowed_setModeByUid() {
305         listModel.setModeByUid = true
306         val record = listModel.transformItem(APP)
307 
308         listModel.setAllowed(record = record, newAllowed = true)
309 
310         verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED)
311     }
312 
313     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
314         lateinit var isAllowedState: State<Boolean?>
315         composeTestRule.setContent { isAllowedState = listModel.isAllowed(record) }
316         return isAllowedState.value
317     }
318 
319     private inner class TestAppOpPermissionAppListModel :
320         AppOpPermissionListModel(context, packageManagers) {
321         override val pageTitleResId = R.string.test_app_op_permission_title
322         override val switchTitleResId = R.string.test_app_op_permission_switch_title
323         override val footerResId = R.string.test_app_op_permission_footer
324 
325         override val appOp = AppOpsManager.OP_MANAGE_MEDIA
326         override val permission = PERMISSION
327         override var broaderPermission: String? = null
328 
329         override var setModeByUid = false
330     }
331 
332     private companion object {
333         const val USER_ID = 0
334         const val PACKAGE_NAME = "package.name"
335         const val PERMISSION = "PERMISSION"
336         const val BROADER_PERMISSION = "BROADER_PERMISSION"
337         val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME }
338         val NOT_CHANGEABLE_APP = ApplicationInfo().apply { packageName = "android" }
339     }
340 }
341 
342 private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
343     var setAllowedCalledWith: Boolean? = null
344 
345     override val mode = MutableLiveData(fakeMode)
346 
347     override fun setAllowed(allowed: Boolean) {
348         setAllowedCalledWith = allowed
349     }
350 
351     override fun getMode() = fakeMode
352 }
353