Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 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.systemui.privacy |
| 18 | |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 19 | import android.app.ActivityManager |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 20 | import android.app.AppOpsManager |
Fabian Kozynski | 8176539 | 2019-02-11 12:38:26 -0500 | [diff] [blame] | 21 | import android.content.Context |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 22 | import android.content.Intent |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 23 | import android.content.pm.UserInfo |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 24 | import android.os.Handler |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 25 | import android.os.UserHandle |
| 26 | import android.os.UserManager |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 27 | import android.support.test.filters.SmallTest |
| 28 | import android.testing.AndroidTestingRunner |
| 29 | import android.testing.TestableLooper |
| 30 | import android.testing.TestableLooper.RunWithLooper |
| 31 | import com.android.systemui.Dependency |
Fabian Kozynski | 8176539 | 2019-02-11 12:38:26 -0500 | [diff] [blame] | 32 | import com.android.systemui.Dependency.BG_HANDLER |
| 33 | import com.android.systemui.Dependency.MAIN_HANDLER |
Fabian Kozynski | 508422b | 2018-12-20 10:58:17 -0500 | [diff] [blame] | 34 | import com.android.systemui.R |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 35 | import com.android.systemui.SysuiTestCase |
| 36 | import com.android.systemui.appops.AppOpItem |
| 37 | import com.android.systemui.appops.AppOpsController |
Fabian Kozynski | a6ff80b | 2019-02-12 11:32:44 -0500 | [diff] [blame] | 38 | import org.hamcrest.Matchers.hasItem |
| 39 | import org.hamcrest.Matchers.not |
| 40 | import org.hamcrest.Matchers.nullValue |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 41 | import org.junit.Assert.assertEquals |
Fabian Kozynski | a6ff80b | 2019-02-12 11:32:44 -0500 | [diff] [blame] | 42 | import org.junit.Assert.assertThat |
| 43 | import org.junit.Assert.assertTrue |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 44 | import org.junit.Before |
| 45 | import org.junit.Test |
| 46 | import org.junit.runner.RunWith |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 47 | import org.mockito.ArgumentCaptor |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 48 | import org.mockito.ArgumentMatchers.any |
| 49 | import org.mockito.ArgumentMatchers.anyInt |
| 50 | import org.mockito.ArgumentMatchers.anyList |
| 51 | import org.mockito.ArgumentMatchers.eq |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 52 | import org.mockito.Captor |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 53 | import org.mockito.Mock |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 54 | import org.mockito.Mockito.atLeastOnce |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 55 | import org.mockito.Mockito.doReturn |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 56 | import org.mockito.Mockito.mock |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 57 | import org.mockito.Mockito.never |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 58 | import org.mockito.Mockito.reset |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 59 | import org.mockito.Mockito.spy |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 60 | import org.mockito.Mockito.verify |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 61 | import org.mockito.Mockito.verifyNoMoreInteractions |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 62 | import org.mockito.MockitoAnnotations |
| 63 | |
| 64 | @RunWith(AndroidTestingRunner::class) |
| 65 | @SmallTest |
| 66 | @RunWithLooper |
| 67 | class PrivacyItemControllerTest : SysuiTestCase() { |
| 68 | |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 69 | companion object { |
| 70 | val CURRENT_USER_ID = ActivityManager.getCurrentUser() |
Fabian Kozynski | 508422b | 2018-12-20 10:58:17 -0500 | [diff] [blame] | 71 | val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE |
| 72 | const val SYSTEM_UID = 1000 |
| 73 | const val TEST_PACKAGE_NAME = "test" |
| 74 | const val DEVICE_SERVICES_STRING = "Device services" |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 75 | const val TAG = "PrivacyItemControllerTest" |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 76 | fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 77 | } |
| 78 | |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 79 | @Mock |
| 80 | private lateinit var appOpsController: AppOpsController |
| 81 | @Mock |
| 82 | private lateinit var callback: PrivacyItemController.Callback |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 83 | @Mock |
| 84 | private lateinit var userManager: UserManager |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 85 | @Captor |
| 86 | private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>> |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 87 | @Captor |
| 88 | private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback> |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 89 | |
| 90 | private lateinit var testableLooper: TestableLooper |
| 91 | private lateinit var privacyItemController: PrivacyItemController |
Fabian Kozynski | 8176539 | 2019-02-11 12:38:26 -0500 | [diff] [blame] | 92 | private lateinit var handler: Handler |
| 93 | |
| 94 | fun PrivacyItemController(context: Context) = |
| 95 | PrivacyItemController(context, appOpsController, handler, handler) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 96 | |
| 97 | @Before |
| 98 | fun setup() { |
| 99 | MockitoAnnotations.initMocks(this) |
| 100 | testableLooper = TestableLooper.get(this) |
Fabian Kozynski | 8176539 | 2019-02-11 12:38:26 -0500 | [diff] [blame] | 101 | handler = Handler(testableLooper.looper) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 102 | |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 103 | appOpsController = mDependency.injectMockDependency(AppOpsController::class.java) |
Fabian Kozynski | 8176539 | 2019-02-11 12:38:26 -0500 | [diff] [blame] | 104 | mDependency.injectTestDependency(Dependency.BG_HANDLER, handler) |
| 105 | mDependency.injectTestDependency(Dependency.MAIN_HANDLER, handler) |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 106 | mContext.addMockSystemService(UserManager::class.java, userManager) |
Fabian Kozynski | 508422b | 2018-12-20 10:58:17 -0500 | [diff] [blame] | 107 | mContext.getOrCreateTestableResources().addOverride(R.string.device_services, |
| 108 | DEVICE_SERVICES_STRING) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 109 | |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 110 | doReturn(listOf(object : UserInfo() { |
| 111 | init { |
| 112 | id = CURRENT_USER_ID |
| 113 | } |
| 114 | })).`when`(userManager).getProfiles(anyInt()) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 115 | |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 116 | privacyItemController = PrivacyItemController(mContext) |
| 117 | } |
| 118 | |
| 119 | @Test |
| 120 | fun testSetListeningTrueByAddingCallback() { |
| 121 | privacyItemController.addCallback(callback) |
| 122 | verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), |
| 123 | any(AppOpsController.Callback::class.java)) |
| 124 | testableLooper.processAllMessages() |
| 125 | verify(callback).privacyChanged(anyList()) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 126 | } |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 127 | |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 128 | @Test |
| 129 | fun testSetListeningTrue() { |
| 130 | privacyItemController.setListening(true) |
| 131 | verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), |
| 132 | any(AppOpsController.Callback::class.java)) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 133 | } |
| 134 | |
| 135 | @Test |
| 136 | fun testSetListeningFalse() { |
| 137 | privacyItemController.setListening(true) |
| 138 | privacyItemController.setListening(false) |
| 139 | verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS), |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 140 | any(AppOpsController.Callback::class.java)) |
| 141 | } |
| 142 | |
| 143 | @Test |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 144 | fun testDistinctItems() { |
Fabian Kozynski | 508422b | 2018-12-20 10:58:17 -0500 | [diff] [blame] | 145 | doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), |
| 146 | AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 147 | .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) |
| 148 | |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 149 | privacyItemController.addCallback(callback) |
Fabian Kozynski | 510585d | 2018-12-19 13:59:17 -0500 | [diff] [blame] | 150 | testableLooper.processAllMessages() |
| 151 | verify(callback).privacyChanged(capture(argCaptor)) |
| 152 | assertEquals(1, argCaptor.value.size) |
| 153 | } |
| 154 | |
| 155 | @Test |
Fabian Kozynski | 508422b | 2018-12-20 10:58:17 -0500 | [diff] [blame] | 156 | fun testSystemApps() { |
| 157 | doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME, |
| 158 | 0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 159 | privacyItemController.addCallback(callback) |
Fabian Kozynski | 508422b | 2018-12-20 10:58:17 -0500 | [diff] [blame] | 160 | testableLooper.processAllMessages() |
| 161 | verify(callback).privacyChanged(capture(argCaptor)) |
| 162 | assertEquals(1, argCaptor.value.size) |
| 163 | assertEquals(context.getString(R.string.device_services), |
| 164 | argCaptor.value[0].application.applicationName) |
| 165 | } |
| 166 | |
| 167 | @Test |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 168 | fun testRegisterReceiver_allUsers() { |
| 169 | val spiedContext = spy(mContext) |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 170 | val itemController = PrivacyItemController(spiedContext) |
| 171 | itemController.setListening(true) |
Fabian Kozynski | b5625ac | 2018-11-21 08:56:55 -0500 | [diff] [blame] | 172 | verify(spiedContext, atLeastOnce()).registerReceiverAsUser( |
| 173 | eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null), |
| 174 | eq(null)) |
| 175 | verify(spiedContext, never()).unregisterReceiver(eq(itemController.userSwitcherReceiver)) |
| 176 | } |
| 177 | |
| 178 | @Test |
| 179 | fun testReceiver_ACTION_USER_FOREGROUND() { |
| 180 | privacyItemController.userSwitcherReceiver.onReceive(context, |
| 181 | Intent(Intent.ACTION_USER_FOREGROUND)) |
| 182 | verify(userManager).getProfiles(anyInt()) |
| 183 | } |
| 184 | |
| 185 | @Test |
| 186 | fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() { |
| 187 | privacyItemController.userSwitcherReceiver.onReceive(context, |
| 188 | Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)) |
| 189 | verify(userManager).getProfiles(anyInt()) |
| 190 | } |
| 191 | |
| 192 | @Test |
| 193 | fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() { |
| 194 | privacyItemController.userSwitcherReceiver.onReceive(context, |
| 195 | Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)) |
| 196 | verify(userManager).getProfiles(anyInt()) |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 197 | } |
Fabian Kozynski | 04f83eb | 2019-01-22 10:38:40 -0500 | [diff] [blame] | 198 | |
| 199 | @Test |
| 200 | fun testAddMultipleCallbacks() { |
| 201 | val otherCallback = mock(PrivacyItemController.Callback::class.java) |
| 202 | privacyItemController.addCallback(callback) |
| 203 | testableLooper.processAllMessages() |
| 204 | verify(callback).privacyChanged(anyList()) |
| 205 | |
| 206 | privacyItemController.addCallback(otherCallback) |
| 207 | testableLooper.processAllMessages() |
| 208 | verify(otherCallback).privacyChanged(anyList()) |
| 209 | // Adding a callback should not unnecessarily call previous ones |
| 210 | verifyNoMoreInteractions(callback) |
| 211 | } |
| 212 | |
| 213 | @Test |
| 214 | fun testMultipleCallbacksAreUpdated() { |
| 215 | doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) |
| 216 | |
| 217 | val otherCallback = mock(PrivacyItemController.Callback::class.java) |
| 218 | privacyItemController.addCallback(callback) |
| 219 | privacyItemController.addCallback(otherCallback) |
| 220 | testableLooper.processAllMessages() |
| 221 | reset(callback) |
| 222 | reset(otherCallback) |
| 223 | |
| 224 | verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback)) |
| 225 | argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) |
| 226 | testableLooper.processAllMessages() |
| 227 | verify(callback).privacyChanged(anyList()) |
| 228 | verify(otherCallback).privacyChanged(anyList()) |
| 229 | } |
| 230 | |
| 231 | @Test |
| 232 | fun testRemoveCallback() { |
| 233 | doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) |
| 234 | val otherCallback = mock(PrivacyItemController.Callback::class.java) |
| 235 | privacyItemController.addCallback(callback) |
| 236 | privacyItemController.addCallback(otherCallback) |
| 237 | testableLooper.processAllMessages() |
| 238 | reset(callback) |
| 239 | reset(otherCallback) |
| 240 | |
| 241 | verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback)) |
| 242 | privacyItemController.removeCallback(callback) |
| 243 | argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) |
| 244 | testableLooper.processAllMessages() |
| 245 | verify(callback, never()).privacyChanged(anyList()) |
| 246 | verify(otherCallback).privacyChanged(anyList()) |
| 247 | } |
Fabian Kozynski | a6ff80b | 2019-02-12 11:32:44 -0500 | [diff] [blame] | 248 | |
| 249 | @Test |
| 250 | fun testListShouldNotHaveNull() { |
| 251 | doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0), |
| 252 | AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) |
| 253 | .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) |
| 254 | privacyItemController.addCallback(callback) |
| 255 | testableLooper.processAllMessages() |
| 256 | |
| 257 | verify(callback).privacyChanged(capture(argCaptor)) |
| 258 | assertEquals(1, argCaptor.value.size) |
| 259 | assertThat(argCaptor.value, not(hasItem(nullValue()))) |
| 260 | } |
| 261 | |
| 262 | @Test |
| 263 | fun testListShouldBeCopy() { |
| 264 | val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA, |
| 265 | PrivacyApplication("", TEST_UID, mContext))) |
| 266 | privacyItemController.privacyList = list |
| 267 | assertEquals(list, privacyItemController.privacyList) |
| 268 | assertTrue(list !== privacyItemController.privacyList) |
| 269 | } |
Fabian Kozynski | 8d06c71 | 2018-11-07 10:33:02 -0500 | [diff] [blame] | 270 | } |