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