1 /* 2 * Copyright (C) 2021 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 19 import android.content.Intent 20 import android.testing.AndroidTestingRunner 21 import android.testing.TestableLooper 22 import android.view.View 23 import android.view.ViewGroup 24 import android.view.ViewGroup.LayoutParams.MATCH_PARENT 25 import android.widget.TextView 26 import androidx.test.filters.SmallTest 27 import com.android.systemui.R 28 import com.android.systemui.SysuiTestCase 29 import com.google.common.truth.Truth.assertThat 30 import org.junit.After 31 import org.junit.Before 32 import org.junit.Test 33 import org.junit.runner.RunWith 34 import org.mockito.Mock 35 import org.mockito.Mockito.mock 36 import org.mockito.Mockito.never 37 import org.mockito.Mockito.verify 38 import org.mockito.MockitoAnnotations 39 40 @SmallTest 41 @RunWith(AndroidTestingRunner::class) 42 @TestableLooper.RunWithLooper(setAsMainLooper = true) 43 class PrivacyDialogV2Test : SysuiTestCase() { 44 45 companion object { 46 private const val TEST_PACKAGE_NAME = "test_pkg" 47 private const val TEST_USER_ID = 0 48 private const val TEST_PERM_GROUP = "test_perm_group" 49 50 private val TEST_INTENT = Intent("test_intent_action") 51 52 private fun createPrivacyElement( 53 type: PrivacyType = PrivacyType.TYPE_MICROPHONE, 54 packageName: String = TEST_PACKAGE_NAME, 55 userId: Int = TEST_USER_ID, 56 applicationName: CharSequence = "App", 57 attributionTag: CharSequence? = null, 58 attributionLabel: CharSequence? = null, 59 proxyLabel: CharSequence? = null, 60 lastActiveTimestamp: Long = 0L, 61 isActive: Boolean = false, 62 isPhoneCall: Boolean = false, 63 isService: Boolean = false, 64 permGroupName: String = TEST_PERM_GROUP, 65 navigationIntent: Intent = TEST_INTENT 66 ) = 67 PrivacyDialogV2.PrivacyElement( 68 type, 69 packageName, 70 userId, 71 applicationName, 72 attributionTag, 73 attributionLabel, 74 proxyLabel, 75 lastActiveTimestamp, 76 isActive, 77 isPhoneCall, 78 isService, 79 permGroupName, 80 navigationIntent 81 ) 82 } 83 84 @Mock private lateinit var manageApp: (String, Int, Intent) -> Unit 85 @Mock private lateinit var closeApp: (String, Int) -> Unit 86 @Mock private lateinit var openPrivacyDashboard: () -> Unit 87 private lateinit var dialog: PrivacyDialogV2 88 89 @Before 90 fun setUp() { 91 MockitoAnnotations.initMocks(this) 92 } 93 94 @After 95 fun teardown() { 96 if (this::dialog.isInitialized) { 97 dialog.dismiss() 98 } 99 } 100 101 @Test 102 fun testManageAppCalledWithCorrectParams() { 103 val list = listOf(createPrivacyElement()) 104 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 105 dialog.show() 106 107 dialog.requireViewById<View>(R.id.privacy_dialog_manage_app_button).callOnClick() 108 109 verify(manageApp).invoke(TEST_PACKAGE_NAME, TEST_USER_ID, TEST_INTENT) 110 } 111 112 @Test 113 fun testCloseAppCalledWithCorrectParams() { 114 val list = listOf(createPrivacyElement(isActive = true)) 115 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 116 dialog.show() 117 118 dialog.requireViewById<View>(R.id.privacy_dialog_close_app_button).callOnClick() 119 120 verify(closeApp).invoke(TEST_PACKAGE_NAME, TEST_USER_ID) 121 } 122 123 @Test 124 fun testCloseAppMissingForService() { 125 val list = listOf(createPrivacyElement(isActive = true, isService = true)) 126 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 127 128 dialog.show() 129 130 assertThat(dialog.findViewById<View>(R.id.privacy_dialog_manage_app_button)).isNotNull() 131 assertThat(dialog.findViewById<View>(R.id.privacy_dialog_close_app_button)).isNull() 132 } 133 134 @Test 135 fun testMoreButton() { 136 dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard) 137 dialog.show() 138 139 dialog.requireViewById<View>(R.id.privacy_dialog_more_button).callOnClick() 140 141 verify(openPrivacyDashboard).invoke() 142 } 143 144 @Test 145 fun testCloseButton() { 146 dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard) 147 val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java) 148 dialog.addOnDismissListener(dismissListener) 149 dialog.show() 150 verify(dismissListener, never()).onDialogDismissed() 151 152 dialog.requireViewById<View>(R.id.privacy_dialog_close_button).callOnClick() 153 154 verify(dismissListener).onDialogDismissed() 155 } 156 157 @Test 158 fun testDismissListenerCalledOnDismiss() { 159 dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard) 160 val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java) 161 dialog.addOnDismissListener(dismissListener) 162 dialog.show() 163 verify(dismissListener, never()).onDialogDismissed() 164 165 dialog.dismiss() 166 167 verify(dismissListener).onDialogDismissed() 168 } 169 170 @Test 171 fun testDismissListenerCalledImmediatelyIfDialogAlreadyDismissed() { 172 dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard) 173 val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java) 174 dialog.show() 175 dialog.dismiss() 176 177 dialog.addOnDismissListener(dismissListener) 178 179 verify(dismissListener).onDialogDismissed() 180 } 181 182 @Test 183 fun testCorrectNumElements() { 184 val list = 185 listOf( 186 createPrivacyElement(type = PrivacyType.TYPE_CAMERA, isActive = true), 187 createPrivacyElement() 188 ) 189 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 190 191 dialog.show() 192 193 assertThat( 194 dialog.requireViewById<ViewGroup>(R.id.privacy_dialog_items_container).childCount 195 ) 196 .isEqualTo(2) 197 } 198 199 @Test 200 fun testHeaderText() { 201 val list = listOf(createPrivacyElement()) 202 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 203 204 dialog.show() 205 206 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_title).text) 207 .isEqualTo(TEST_PERM_GROUP) 208 } 209 210 @Test 211 fun testUsingText() { 212 val list = listOf(createPrivacyElement(type = PrivacyType.TYPE_CAMERA, isActive = true)) 213 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 214 215 dialog.show() 216 217 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text) 218 .isEqualTo("In use by App") 219 } 220 221 @Test 222 fun testRecentText() { 223 val list = listOf(createPrivacyElement()) 224 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 225 226 dialog.show() 227 228 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text) 229 .isEqualTo("Recently used by App") 230 } 231 232 @Test 233 fun testPhoneCall() { 234 val list = listOf(createPrivacyElement(isPhoneCall = true)) 235 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 236 237 dialog.show() 238 239 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text) 240 .isEqualTo("Recently used in phone call") 241 } 242 243 @Test 244 fun testPhoneCallNotClickable() { 245 val list = listOf(createPrivacyElement(isPhoneCall = true)) 246 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 247 248 dialog.show() 249 250 assertThat(dialog.requireViewById<View>(R.id.privacy_dialog_item_card).isClickable) 251 .isFalse() 252 assertThat( 253 dialog 254 .requireViewById<View>(R.id.privacy_dialog_item_header_expand_toggle) 255 .visibility 256 ) 257 .isEqualTo(View.GONE) 258 } 259 260 @Test 261 fun testProxyLabel() { 262 val list = listOf(createPrivacyElement(proxyLabel = "proxy label")) 263 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 264 265 dialog.show() 266 267 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text) 268 .isEqualTo("Recently used by App (proxy label)") 269 } 270 271 @Test 272 fun testSubattribution() { 273 val list = 274 listOf( 275 createPrivacyElement( 276 attributionLabel = "For subattribution", 277 isActive = true, 278 isService = true 279 ) 280 ) 281 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 282 283 dialog.show() 284 285 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text) 286 .isEqualTo("In use by App (For subattribution)") 287 } 288 289 @Test 290 fun testSubattributionAndProxyLabel() { 291 val list = 292 listOf( 293 createPrivacyElement( 294 attributionLabel = "For subattribution", 295 proxyLabel = "proxy label", 296 isActive = true 297 ) 298 ) 299 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 300 dialog.show() 301 assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text) 302 .isEqualTo("In use by App (For subattribution \u2022 proxy label)") 303 } 304 305 @Test 306 fun testDialogHasTitle() { 307 val list = listOf(createPrivacyElement()) 308 dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard) 309 dialog.show() 310 311 assertThat(dialog.window?.attributes?.title).isEqualTo("Microphone & Camera") 312 } 313 314 @Test 315 fun testDialogIsFullscreen() { 316 dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard) 317 dialog.show() 318 319 assertThat(dialog.window?.attributes?.width).isEqualTo(MATCH_PARENT) 320 assertThat(dialog.window?.attributes?.height).isEqualTo(MATCH_PARENT) 321 } 322 } 323