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.qs.external 18 19 import android.app.IUriGrantsManager 20 import android.app.StatusBarManager 21 import android.content.ComponentName 22 import android.content.DialogInterface 23 import android.graphics.drawable.Icon 24 import android.os.RemoteException 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import androidx.test.filters.SmallTest 27 import com.android.internal.logging.InstanceId 28 import com.android.internal.statusbar.IAddTileResultCallback 29 import com.android.systemui.InstanceIdSequenceFake 30 import com.android.systemui.SysuiTestCase 31 import com.android.systemui.qs.QSHost 32 import com.android.systemui.statusbar.CommandQueue 33 import com.android.systemui.statusbar.commandline.CommandRegistry 34 import com.android.systemui.util.mockito.any 35 import com.android.systemui.util.mockito.capture 36 import com.android.systemui.util.mockito.eq 37 import com.google.common.truth.Truth.assertThat 38 import java.util.function.Consumer 39 import org.junit.Before 40 import org.junit.Test 41 import org.junit.runner.RunWith 42 import org.mockito.ArgumentCaptor 43 import org.mockito.Mock 44 import org.mockito.Mockito.anyBoolean 45 import org.mockito.Mockito.anyInt 46 import org.mockito.Mockito.anyString 47 import org.mockito.Mockito.atLeastOnce 48 import org.mockito.Mockito.never 49 import org.mockito.Mockito.verify 50 import org.mockito.Mockito.`when` 51 import org.mockito.MockitoAnnotations 52 53 @SmallTest 54 @RunWith(AndroidJUnit4::class) 55 class TileServiceRequestControllerTest : SysuiTestCase() { 56 57 companion object { 58 private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls") 59 private const val TEST_APP_NAME = "App" 60 private const val TEST_LABEL = "Label" 61 private const val TEST_UID = 12345 62 } 63 64 @Mock 65 private lateinit var tileRequestDialog: TileRequestDialog 66 @Mock 67 private lateinit var qsHost: QSHost 68 @Mock 69 private lateinit var commandRegistry: CommandRegistry 70 @Mock 71 private lateinit var commandQueue: CommandQueue 72 @Mock 73 private lateinit var logger: TileRequestDialogEventLogger 74 @Mock 75 private lateinit var icon: Icon 76 @Mock 77 private lateinit var ugm: IUriGrantsManager 78 79 private val instanceIdSequence = InstanceIdSequenceFake(1_000) 80 private lateinit var controller: TileServiceRequestController 81 82 @Before setUpnull83 fun setUp() { 84 MockitoAnnotations.initMocks(this) 85 86 `when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) 87 88 // Tile not present by default 89 `when`(qsHost.indexOf(anyString())).thenReturn(-1) 90 91 controller = TileServiceRequestController( 92 qsHost, 93 commandQueue, 94 commandRegistry, 95 logger, 96 ugm, 97 ) { 98 tileRequestDialog 99 } 100 101 controller.init() 102 } 103 104 @Test requestTileAdd_dataIsPassedToDialognull105 fun requestTileAdd_dataIsPassedToDialog() { 106 controller.requestTileAdd( 107 TEST_UID, 108 TEST_COMPONENT, 109 TEST_APP_NAME, 110 TEST_LABEL, 111 icon, 112 Callback(), 113 ) 114 115 verify(tileRequestDialog).setTileData( 116 TileRequestDialog.TileData( 117 TEST_UID, 118 TEST_APP_NAME, 119 TEST_LABEL, 120 icon, 121 TEST_COMPONENT.packageName, 122 ), 123 ugm, 124 ) 125 } 126 127 @Test tileAlreadyAdded_correctResultnull128 fun tileAlreadyAdded_correctResult() { 129 `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) 130 131 val callback = Callback() 132 controller.requestTileAdd( 133 TEST_UID, 134 TEST_COMPONENT, 135 TEST_APP_NAME, 136 TEST_LABEL, 137 icon, 138 callback, 139 ) 140 141 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) 142 verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean()) 143 } 144 145 @Test tileAlreadyAdded_loggednull146 fun tileAlreadyAdded_logged() { 147 `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) 148 149 controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} 150 151 verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any()) 152 verify(logger, never()).logDialogShown(anyString(), any()) 153 verify(logger, never()).logUserResponse(anyInt(), anyString(), any()) 154 } 155 156 @Test showAllUsers_setnull157 fun showAllUsers_set() { 158 controller.requestTileAdd( 159 TEST_UID, 160 TEST_COMPONENT, 161 TEST_APP_NAME, 162 TEST_LABEL, 163 icon, 164 Callback(), 165 ) 166 verify(tileRequestDialog).setShowForAllUsers(true) 167 } 168 169 @Test cancelOnTouchOutside_setnull170 fun cancelOnTouchOutside_set() { 171 controller.requestTileAdd( 172 TEST_UID, 173 TEST_COMPONENT, 174 TEST_APP_NAME, 175 TEST_LABEL, 176 icon, 177 Callback(), 178 ) 179 verify(tileRequestDialog).setCanceledOnTouchOutside(true) 180 } 181 182 @Test dialogShown_loggednull183 fun dialogShown_logged() { 184 controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} 185 186 verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any()) 187 } 188 189 @Test cancelListener_dismissResultnull190 fun cancelListener_dismissResult() { 191 val cancelListenerCaptor = 192 ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) 193 194 val callback = Callback() 195 controller.requestTileAdd( 196 TEST_UID, 197 TEST_COMPONENT, 198 TEST_APP_NAME, 199 TEST_LABEL, 200 icon, 201 callback, 202 ) 203 verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) 204 205 cancelListenerCaptor.value.onCancel(tileRequestDialog) 206 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) 207 verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean()) 208 } 209 210 @Test dialogCancelled_loggednull211 fun dialogCancelled_logged() { 212 val cancelListenerCaptor = 213 ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) 214 215 controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} 216 val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) 217 218 verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) 219 verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId) 220 221 cancelListenerCaptor.value.onCancel(tileRequestDialog) 222 verify(logger).logUserResponse( 223 StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED, 224 TEST_COMPONENT.packageName, 225 instanceId 226 ) 227 } 228 229 @Test positiveActionListener_tileAddedResultnull230 fun positiveActionListener_tileAddedResult() { 231 val clickListenerCaptor = 232 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) 233 234 val callback = Callback() 235 controller.requestTileAdd( 236 TEST_UID, 237 TEST_COMPONENT, 238 TEST_APP_NAME, 239 TEST_LABEL, 240 icon, 241 callback, 242 ) 243 verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) 244 245 clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) 246 247 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE) 248 verify(qsHost).addTile(TEST_COMPONENT, /* end */ true) 249 } 250 251 @Test tileAdded_loggednull252 fun tileAdded_logged() { 253 val clickListenerCaptor = 254 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) 255 256 controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} 257 val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) 258 259 verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) 260 verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId) 261 262 clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) 263 verify(logger).logUserResponse( 264 StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, 265 TEST_COMPONENT.packageName, 266 instanceId 267 ) 268 } 269 270 @Test negativeActionListener_tileNotAddedResultnull271 fun negativeActionListener_tileNotAddedResult() { 272 val clickListenerCaptor = 273 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) 274 275 val callback = Callback() 276 controller.requestTileAdd( 277 TEST_UID, 278 TEST_COMPONENT, 279 TEST_APP_NAME, 280 TEST_LABEL, 281 icon, 282 callback, 283 ) 284 verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor)) 285 286 clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE) 287 288 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DONT_ADD_TILE) 289 verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean()) 290 } 291 292 @Test tileNotAdded_loggednull293 fun tileNotAdded_logged() { 294 val clickListenerCaptor = 295 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) 296 297 controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} 298 val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) 299 300 verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor)) 301 verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId) 302 303 clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE) 304 verify(logger).logUserResponse( 305 StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED, 306 TEST_COMPONENT.packageName, 307 instanceId 308 ) 309 } 310 311 @Test commandQueueCallback_registerednull312 fun commandQueueCallback_registered() { 313 verify(commandQueue).addCallback(any()) 314 } 315 316 @Test commandQueueCallback_dataPassedToDialognull317 fun commandQueueCallback_dataPassedToDialog() { 318 val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 319 verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) 320 321 captor.value.requestAddTile( 322 TEST_UID, 323 TEST_COMPONENT, 324 TEST_APP_NAME, 325 TEST_LABEL, 326 icon, 327 Callback(), 328 ) 329 330 verify(tileRequestDialog).setTileData( 331 TileRequestDialog.TileData( 332 TEST_UID, 333 TEST_APP_NAME, 334 TEST_LABEL, 335 icon, 336 TEST_COMPONENT.packageName, 337 ), 338 ugm, 339 ) 340 } 341 342 @Test commandQueueCallback_callbackCallednull343 fun commandQueueCallback_callbackCalled() { 344 `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) 345 val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 346 verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) 347 val c = Callback() 348 349 captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c) 350 351 assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) 352 } 353 354 @Test interfaceThrowsRemoteException_doesntCrashnull355 fun interfaceThrowsRemoteException_doesntCrash() { 356 val cancelListenerCaptor = 357 ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) 358 val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 359 verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) 360 361 val callback = object : IAddTileResultCallback.Stub() { 362 override fun onTileRequest(p0: Int) { 363 throw RemoteException() 364 } 365 } 366 captor.value.requestAddTile( 367 TEST_UID, 368 TEST_COMPONENT, 369 TEST_APP_NAME, 370 TEST_LABEL, 371 icon, 372 callback, 373 ) 374 verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) 375 376 cancelListenerCaptor.value.onCancel(tileRequestDialog) 377 } 378 379 @Test testDismissDialogResponsenull380 fun testDismissDialogResponse() { 381 val dismissListenerCaptor = 382 ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) 383 384 val callback = Callback() 385 controller.requestTileAdd( 386 TEST_UID, 387 TEST_COMPONENT, 388 TEST_APP_NAME, 389 TEST_LABEL, 390 icon, 391 callback, 392 ) 393 verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) 394 395 dismissListenerCaptor.value.onDismiss(tileRequestDialog) 396 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) 397 } 398 399 @Test addTileAndThenDismissSendsOnlyAddTilenull400 fun addTileAndThenDismissSendsOnlyAddTile() { 401 // After clicking, the dialog is dismissed. This tests that only one response 402 // is sent (the first one) 403 val dismissListenerCaptor = 404 ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) 405 val clickListenerCaptor = 406 ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) 407 408 val callback = Callback() 409 controller.requestTileAdd( 410 TEST_UID, 411 TEST_COMPONENT, 412 TEST_APP_NAME, 413 TEST_LABEL, 414 icon, 415 callback, 416 ) 417 verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) 418 verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) 419 420 clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) 421 dismissListenerCaptor.value.onDismiss(tileRequestDialog) 422 423 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE) 424 assertThat(callback.timesCalled).isEqualTo(1) 425 } 426 427 @Test cancelAndThenDismissSendsOnlyOncenull428 fun cancelAndThenDismissSendsOnlyOnce() { 429 // After cancelling, the dialog is dismissed. This tests that only one response 430 // is sent. 431 val dismissListenerCaptor = 432 ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) 433 val cancelListenerCaptor = 434 ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) 435 436 val callback = Callback() 437 controller.requestTileAdd( 438 TEST_UID, 439 TEST_COMPONENT, 440 TEST_APP_NAME, 441 TEST_LABEL, 442 icon, 443 callback, 444 ) 445 verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) 446 verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) 447 448 cancelListenerCaptor.value.onCancel(tileRequestDialog) 449 dismissListenerCaptor.value.onDismiss(tileRequestDialog) 450 451 assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) 452 assertThat(callback.timesCalled).isEqualTo(1) 453 } 454 455 private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> { 456 var lastAccepted: Int? = null 457 private set 458 459 var timesCalled = 0 460 private set 461 acceptnull462 override fun accept(t: Int) { 463 lastAccepted = t 464 timesCalled++ 465 } 466 onTileRequestnull467 override fun onTileRequest(r: Int) { 468 accept(r) 469 } 470 } 471 } 472