1 /* <lambda>null2 * Copyright (C) 2024 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.car.docklib 18 19 import android.app.ActivityManager 20 import android.car.content.pm.CarPackageManager 21 import android.content.ComponentName 22 import android.content.Context 23 import android.content.Intent 24 import android.content.pm.ActivityInfo 25 import android.content.pm.PackageInfo 26 import android.content.pm.PackageManager 27 import android.content.pm.ServiceInfo 28 import android.content.res.Resources 29 import android.graphics.Bitmap 30 import android.graphics.drawable.Drawable 31 import androidx.arch.core.executor.testing.InstantTaskExecutorRule 32 import androidx.test.ext.junit.runners.AndroidJUnit4 33 import com.android.car.docklib.data.DockAppItem 34 import com.android.car.docklib.data.DockProtoDataController 35 import com.android.launcher3.icons.IconFactory 36 import com.google.common.truth.Truth.assertThat 37 import java.util.UUID 38 import org.junit.Assert.assertThrows 39 import org.junit.Before 40 import org.junit.Rule 41 import org.junit.Test 42 import org.junit.runner.RunWith 43 import org.mockito.ArgumentMatchers.anyInt 44 import org.mockito.kotlin.any 45 import org.mockito.kotlin.doNothing 46 import org.mockito.kotlin.doReturn 47 import org.mockito.kotlin.eq 48 import org.mockito.kotlin.mock 49 import org.mockito.kotlin.spy 50 import org.mockito.kotlin.verify 51 import org.mockito.kotlin.whenever 52 53 @RunWith(AndroidJUnit4::class) 54 class DockViewModelTest { 55 private companion object { 56 private const val CURRENT_USER_ID = 10 57 private const val MAX_ITEMS = 4 58 private const val TOAST_STR = "TOAST_STR" 59 } 60 61 @get:Rule 62 val instantTaskExecutorRule = InstantTaskExecutorRule() 63 64 private lateinit var items: List<DockAppItem> 65 private lateinit var model: DockViewModel 66 private val resourcesMock = mock<Resources> {} 67 private val contextMock = mock<Context> { 68 on { getString(eq(R.string.pin_failed_no_spots)) } doReturn TOAST_STR 69 on { resources } doReturn resourcesMock 70 } 71 private val packageManagerMock = mock<PackageManager> { 72 on { 73 queryIntentServices(any<Intent>(), any<PackageManager.ResolveInfoFlags>()) 74 } doReturn listOf() 75 } 76 private var carPackageManagerMock = mock<CarPackageManager> {} 77 private var bitmapMock = mock<Bitmap> {} 78 private var iconFactoryMock = mock<IconFactory> { 79 on { createScaledBitmap(any<Drawable>(), anyInt()) } doReturn bitmapMock 80 } 81 private val dataController = mock<DockProtoDataController>() 82 83 @Before 84 fun setUp() { 85 // explicitly use default items unless saved items are set 86 whenever(dataController.loadFromFile()).thenReturn(null) 87 model = createSpyDockViewModel() 88 doNothing().whenever(model).showToast(any()) 89 } 90 91 @Test 92 fun init_defaultPinnedItems_addedToDock() { 93 val defaultPinnedItems = 94 createTestComponentList(pkgPrefix = "DEFAULT_PKG", classPrefix = "DEFAULT_CLASS") 95 96 model = createSpyDockViewModel(defaultPinnedItems = defaultPinnedItems) 97 98 assertThat(items.size).isEqualTo(MAX_ITEMS) 99 assertThat(items[0].component).isEqualTo(defaultPinnedItems[0]) 100 assertThat(items[1].component).isEqualTo(defaultPinnedItems[1]) 101 assertThat(items[2].component).isEqualTo(defaultPinnedItems[2]) 102 assertThat(items[3].component).isEqualTo(defaultPinnedItems[3]) 103 } 104 105 @Test 106 fun init_savedPinnedItems_noItemAddedToDock() { 107 val defaultPinnedItems = 108 createTestComponentList(pkgPrefix = "DEFAULT_PKG", classPrefix = "DEFAULT_CLASS") 109 val savedPinnedItems = mapOf<Int, ComponentName>() 110 whenever(dataController.loadFromFile()).thenReturn(savedPinnedItems) 111 112 model = createSpyDockViewModel(defaultPinnedItems = defaultPinnedItems) 113 114 assertThat(items.size).isEqualTo(MAX_ITEMS) 115 assertThat(items[0].type).isEqualTo(DockAppItem.Type.DYNAMIC) 116 assertThat(items[1].type).isEqualTo(DockAppItem.Type.DYNAMIC) 117 assertThat(items[2].type).isEqualTo(DockAppItem.Type.DYNAMIC) 118 assertThat(items[3].type).isEqualTo(DockAppItem.Type.DYNAMIC) 119 } 120 121 @Test 122 fun init_savedPinnedItems_onlySavedItemsAddedToDock() { 123 val defaultPinnedItems = 124 createTestComponentList(pkgPrefix = "DEFAULT_PKG", classPrefix = "DEFAULT_CLASS") 125 val savedPinnedItems = 126 mapOf( 127 0 to createNewComponent(pkg = "SAVED_PKG_0", clazz = "SAVED_CLASS_0", false), 128 2 to createNewComponent(pkg = "SAVED_PKG_2", clazz = "SAVED_CLASS_2", false), 129 ) 130 whenever(dataController.loadFromFile()).thenReturn(savedPinnedItems) 131 132 model = createSpyDockViewModel(defaultPinnedItems = defaultPinnedItems) 133 134 assertThat(items.size).isEqualTo(MAX_ITEMS) 135 assertThat(items[0].component).isEqualTo(savedPinnedItems[0]) 136 assertThat(items[1].type).isEqualTo(DockAppItem.Type.DYNAMIC) 137 assertThat(items[2].component).isEqualTo(savedPinnedItems[2]) 138 assertThat(items[3].type).isEqualTo(DockAppItem.Type.DYNAMIC) 139 } 140 141 @Test 142 fun addDynamicItem_emptyDockList_index0Updated() { 143 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 144 145 model.internalItems.clear() 146 model.addDynamicItem(newComponent) 147 148 assertThat(items[0].component).isEqualTo(newComponent) 149 } 150 151 @Test 152 fun addDynamicItem_allItemsDynamic_leastRecentItemUpdated() { 153 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 154 model.internalItems.clear() 155 for (i in 0..<MAX_ITEMS) { 156 model.internalItems[i] = TestUtils.createAppItem( 157 app = "da$i", 158 type = DockAppItem.Type.DYNAMIC) 159 } 160 161 model.addDynamicItem(newComponent) 162 163 assertThat(items.size).isEqualTo(MAX_ITEMS) 164 assertThat(items[0].component).isEqualTo(newComponent) 165 } 166 167 @Test 168 fun addDynamicItem_appInDock_itemsNotChanged() { 169 val existingComponent = createNewComponent(pkg = "da0", clazz = "da0") 170 for (i in 0..<MAX_ITEMS) { 171 model.internalItems[i] = TestUtils.createAppItem( 172 app = "da$i", 173 type = DockAppItem.Type.DYNAMIC 174 ) 175 } 176 177 model.addDynamicItem(existingComponent) 178 179 assertThat(items.size).isEqualTo(MAX_ITEMS) 180 assertThat(items.filter { it.component == existingComponent }.size).isEqualTo(1) 181 } 182 183 @Test 184 fun addDynamicItem_appInDock_recencyRefreshed() { 185 val existingComponent = createNewComponent(pkg = "da0", clazz = "da0") 186 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 187 for (i in 0..<MAX_ITEMS) { 188 model.internalItems[i] = TestUtils.createAppItem( 189 app = "da$i", 190 type = DockAppItem.Type.DYNAMIC 191 ) 192 } 193 194 model.addDynamicItem(existingComponent) 195 model.addDynamicItem(newComponent) 196 197 assertThat(items.size).isEqualTo(MAX_ITEMS) 198 assertThat(items[1].component).isEqualTo(newComponent) 199 } 200 201 @Test 202 fun pinItem_itemWithIdNotInDock_itemNotPinned() { 203 val idNotInDock = UUID.nameUUIDFromBytes("idNotInDock".toByteArray()) 204 for (i in 0..<MAX_ITEMS) { 205 model.internalItems[i] = TestUtils.createAppItem( 206 id = UUID.nameUUIDFromBytes("id$i".toByteArray()), 207 app = "da$i", 208 type = DockAppItem.Type.DYNAMIC 209 ) 210 } 211 212 model.pinItem(idNotInDock) 213 214 items.forEach { assertThat(it.type).isEqualTo(DockAppItem.Type.DYNAMIC) } 215 } 216 217 @Test 218 fun pinItem_itemWithIdInDock_itemTypeStatic_itemIdUnchanged() { 219 val idInDock = UUID.nameUUIDFromBytes("id0".toByteArray()) 220 for (i in 0..<MAX_ITEMS) { 221 model.internalItems[i] = TestUtils.createAppItem( 222 id = UUID.nameUUIDFromBytes("id$i".toByteArray()), 223 app = "da$i", 224 type = DockAppItem.Type.DYNAMIC 225 ) 226 } 227 228 model.pinItem(idInDock) 229 230 val dockItem = items.firstOrNull { it.id == idInDock } 231 assertThat(dockItem).isNotNull() 232 assertThat(dockItem?.type).isEqualTo(DockAppItem.Type.STATIC) 233 } 234 235 @Test 236 fun pinItem_indexLessThanZero_itemNotPinned() { 237 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 238 for (i in 0..<MAX_ITEMS) { 239 model.internalItems[i] = TestUtils.createAppItem( 240 app = "da$i", 241 type = DockAppItem.Type.DYNAMIC 242 ) 243 } 244 245 model.pinItem(newComponent, indexToPin = -1) 246 247 items.forEach { 248 assertThat(it.component).isNotEqualTo(newComponent) 249 } 250 } 251 252 @Test 253 fun pinItem_indexGreaterThanMaxItems_itemNotPinned() { 254 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 255 for (i in 0..<MAX_ITEMS) { 256 model.internalItems[i] = TestUtils.createAppItem( 257 app = "da$i", 258 type = DockAppItem.Type.DYNAMIC 259 ) 260 } 261 262 model.pinItem(newComponent, indexToPin = MAX_ITEMS + 1) 263 264 items.forEach { 265 assertThat(it.component).isNotEqualTo(newComponent) 266 } 267 } 268 269 @Test 270 fun pinItem_indexProvided_itemPinnedToIndex() { 271 val indexToPin = 2 272 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 273 for (i in 0..<MAX_ITEMS) { 274 model.internalItems[i] = TestUtils.createAppItem( 275 app = "da$i", 276 type = DockAppItem.Type.DYNAMIC 277 ) 278 } 279 280 model.pinItem(newComponent, indexToPin) 281 282 assertThat(items[ 283 indexToPin].component).isEqualTo(newComponent) 284 assertThat(items[indexToPin].type).isEqualTo(DockAppItem.Type.STATIC) 285 } 286 287 @Test 288 fun pinItem_indexNotProvided_noDynamicItemOrEmptyIndex_itemNotPinned() { 289 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 290 for (i in 0..<MAX_ITEMS) { 291 model.internalItems[i] = TestUtils.createAppItem( 292 app = "da$i", 293 type = DockAppItem.Type.STATIC 294 ) 295 } 296 297 model.pinItem(newComponent, indexToPin = null) 298 299 items.forEach { assertThat(it.component).isNotEqualTo(newComponent) } 300 } 301 302 @Test 303 fun pinItem_indexNotProvided_noDynamicItemOrEmptyIndex_toastShown() { 304 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 305 for (i in 0..<MAX_ITEMS) { 306 model.internalItems[i] = TestUtils.createAppItem( 307 app = "da$i", 308 type = DockAppItem.Type.STATIC 309 ) 310 } 311 312 model.pinItem(newComponent, indexToPin = null) 313 314 verify(model).showToast(eq(TOAST_STR)) 315 } 316 317 @Test 318 fun pinItem_indexNotProvided_dynamicItemsPresent_itemPinnedToFirstDynamicItemIndex() { 319 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 320 model.internalItems.compute(0) { _, item -> item?.copy(type = DockAppItem.Type.STATIC) } 321 model.internalItems.compute(1) { _, item -> item?.copy(type = DockAppItem.Type.DYNAMIC) } 322 model.internalItems.compute(2) { _, item -> item?.copy(type = DockAppItem.Type.DYNAMIC) } 323 model.internalItems.compute(3) { _, item -> item?.copy(type = DockAppItem.Type.STATIC) } 324 325 model.pinItem(newComponent, indexToPin = null) 326 327 val index = items.indexOfFirst { it.component == newComponent } 328 assertThat(index).isEqualTo(1) 329 assertThat(items[index].type).isEqualTo(DockAppItem.Type.STATIC) 330 } 331 332 @Test 333 fun pinItem_indexNotProvided_emptyIndexesPresent_itemPinnedToFirstEmptyIndex() { 334 val newComponent = createNewComponent(pkg = "newpkg", clazz = "newclass") 335 model.internalItems.replaceAll { _, item -> item.copy(type = DockAppItem.Type.STATIC) } 336 model.internalItems.remove(1) 337 338 model.pinItem(newComponent, indexToPin = null) 339 340 val index = items.indexOfFirst { it.component == newComponent } 341 assertThat(index).isEqualTo(1) 342 assertThat(items[index].type).isEqualTo(DockAppItem.Type.STATIC) 343 } 344 345 @Test 346 fun removeItem_itemWithIdNotInDock_itemNotRemoved() { 347 val idNotInDock = UUID.nameUUIDFromBytes("idNotInDock".toByteArray()) 348 for (i in 0..<MAX_ITEMS) { 349 model.internalItems[i] = TestUtils.createAppItem( 350 id = UUID.nameUUIDFromBytes("id$i".toByteArray()), 351 app = "da$i", 352 type = DockAppItem.Type.STATIC 353 ) 354 } 355 val listBeforeRemove = model.internalItems.values.toList() 356 357 model.removeItem(idNotInDock) 358 359 listBeforeRemove.forEachIndexed { key, item -> assertThat(items[key]).isEqualTo(item) } 360 } 361 362 @Test 363 fun removeItem_itemWithIdInDock_itemRemoved() { 364 val idInDock = UUID.nameUUIDFromBytes("id2".toByteArray()) 365 for (i in 0..<MAX_ITEMS) { 366 model.internalItems[i] = TestUtils.createAppItem( 367 id = UUID.nameUUIDFromBytes("id$i".toByteArray()), 368 app = "da$i", 369 type = DockAppItem.Type.STATIC 370 ) 371 } 372 373 model.removeItem(idInDock) 374 375 items.forEach { item -> assertThat(item.id).isNotEqualTo(idInDock) } 376 } 377 378 @Test 379 fun createDockList_indexNotFilled_noRecentTasks_noLauncherApp_errorThrown() { 380 model = createSpyDockViewModel(launcherActivities = mutableSetOf()) 381 doReturn(List<ActivityManager.RunningTaskInfo>(0) { return }) 382 .whenever(model).getRunningTasks() 383 model.internalItems.remove(2) 384 385 assertThrows(IllegalStateException::class.java) { model.createDockList() } 386 } 387 388 @Test 389 fun createDockList_indexNotFilled_noRecentTasks_launcherActivitiesExcluded_errorThrown() { 390 val cmpList = createTestComponentList(pkgPrefix = "testPkg", classPrefix = "testClass") 391 model = createSpyDockViewModel( 392 launcherActivities = cmpList.toMutableSet(), 393 excludedComponents = cmpList.toSet(), 394 ) 395 doReturn(List<ActivityManager.RunningTaskInfo>(0) { return }) 396 .whenever(model).getRunningTasks() 397 model.internalItems.remove(2) 398 399 assertThrows(IllegalStateException::class.java) { model.createDockList() } 400 } 401 402 @Test 403 fun createDockList_indexNotFilled_noRecentTasks_launcherActivitiesAlreadyInDock_errorThrown() { 404 val launcherAppComponent = createNewComponent(pkg = "testPkg", clazz = "testClass") 405 model = createSpyDockViewModel(launcherActivities = mutableSetOf(launcherAppComponent)) 406 doReturn(List<ActivityManager.RunningTaskInfo>(0) { return }) 407 .whenever(model).getRunningTasks() 408 model.internalItems[2] = TestUtils.createAppItem(component = launcherAppComponent) 409 model.internalItems.remove(3) 410 411 assertThrows(IllegalStateException::class.java) { model.createDockList() } 412 } 413 414 @Test 415 fun createDockList_indexNotFilled_noRecentTasks_randomLauncherAppAdded() { 416 val launcherActivities = createTestComponentList( 417 pkgPrefix = "testPkg", 418 classPrefix = "testClass" 419 ) 420 model = createSpyDockViewModel(launcherActivities = launcherActivities.toMutableSet()) 421 doReturn(List<ActivityManager.RunningTaskInfo>(0) { return }) 422 .whenever(model).getRunningTasks() 423 model.internalItems.remove(2) 424 425 val dockList = model.createDockList() 426 427 assertThat(launcherActivities.contains(dockList[2].component)).isTrue() 428 } 429 430 @Test 431 fun createDockList_indexNotFilled_noRecentTasksForCurrentUser_randomLauncherAppAdded() { 432 val launcherActivities = createTestComponentList( 433 pkgPrefix = "testPkg", 434 classPrefix = "testClass" 435 ) 436 model = createSpyDockViewModel(launcherActivities = launcherActivities.toMutableSet()) 437 doReturn(createTestRunningTaskInfoList(userId = CURRENT_USER_ID + 1)) 438 .whenever(model) 439 .getRunningTasks() 440 model.internalItems.remove(2) 441 442 val dockList = model.createDockList() 443 444 assertThat(launcherActivities.contains(dockList[2].component)).isTrue() 445 } 446 447 @Test 448 fun createDockList_indexNotFilled_recentTasksExcluded_randomLauncherAppAdded() { 449 val launcherActivities = createTestComponentList( 450 pkgPrefix = "testPkg", 451 classPrefix = "testClass" 452 ) 453 val excludedComponent = createNewComponent(pkg = "excludedPkg", clazz = "excludedClass") 454 model = createSpyDockViewModel( 455 launcherActivities = launcherActivities.toMutableSet(), 456 excludedComponents = setOf(excludedComponent), 457 ) 458 doReturn(createTestRunningTaskInfoList(component = excludedComponent)) 459 .whenever(model) 460 .getRunningTasks() 461 model.internalItems.remove(2) 462 463 val dockList = model.createDockList() 464 465 assertThat(launcherActivities.contains(dockList[2].component)).isTrue() 466 } 467 468 @Test 469 fun createDockList_indexNotFilled_recentTasksAlreadyInDock_randomLauncherAppAdded() { 470 val launcherActivities = createTestComponentList( 471 pkgPrefix = "testPkg", 472 classPrefix = "testClass" 473 ) 474 val recentTaskComponent = 475 createNewComponent(pkg = "recentTaskPkg", clazz = "recentTaskClass") 476 model = createSpyDockViewModel(launcherActivities = launcherActivities.toMutableSet()) 477 doReturn(createTestRunningTaskInfoList(component = recentTaskComponent)) 478 .whenever(model) 479 .getRunningTasks() 480 model.internalItems[2] = TestUtils.createAppItem(component = recentTaskComponent) 481 model.internalItems.remove(3) 482 483 val dockList = model.createDockList() 484 485 assertThat(launcherActivities.contains(dockList[3].component)).isTrue() 486 } 487 488 @Test 489 fun createDockList_indexNotFilled_recentTasksAdded() { 490 val recentTaskComponent = 491 createNewComponent(pkg = "recentTaskPkg", clazz = "recentTaskClass") 492 val recentTaskList = createTestRunningTaskInfoList(component = recentTaskComponent) 493 doReturn(recentTaskList).whenever(model).getRunningTasks() 494 model.internalItems.remove(2) 495 496 val dockList = model.createDockList() 497 498 assertThat(dockList[2].component).isEqualTo(recentTaskComponent) 499 } 500 501 @Test 502 fun removeItems_itemsWithPackageNameInDock_itemsRemoved() { 503 val pkgToBeRemoved = "pkgToBeRemoved" 504 val pkgOther = "pkgOther" 505 model.internalItems[0] = 506 TestUtils.createAppItem(type = DockAppItem.Type.DYNAMIC, pkg = pkgToBeRemoved) 507 model.internalItems[1] = 508 TestUtils.createAppItem(type = DockAppItem.Type.STATIC, pkg = pkgOther) 509 model.internalItems[2] = 510 TestUtils.createAppItem(type = DockAppItem.Type.STATIC, pkg = pkgToBeRemoved) 511 model.internalItems[3] = 512 TestUtils.createAppItem(type = DockAppItem.Type.DYNAMIC, pkg = pkgOther) 513 514 model.removeItems(pkgToBeRemoved) 515 516 items.forEach { assertThat(it.component.packageName).isNotEqualTo(pkgToBeRemoved) } 517 } 518 519 @Test 520 fun removeItems_itemsWithPackageNameNotInDock_itemsNotRemoved() { 521 val pkgToBeRemoved = "pkgToBeRemoved" 522 val pkgOther = "pkgOther" 523 model.internalItems[0] = 524 TestUtils.createAppItem(type = DockAppItem.Type.DYNAMIC, pkg = pkgOther) 525 model.internalItems[1] = 526 TestUtils.createAppItem(type = DockAppItem.Type.STATIC, pkg = pkgOther) 527 model.internalItems[2] = 528 TestUtils.createAppItem(type = DockAppItem.Type.STATIC, pkg = pkgOther) 529 model.internalItems[3] = 530 TestUtils.createAppItem(type = DockAppItem.Type.DYNAMIC, pkg = pkgOther) 531 532 model.removeItems(pkgToBeRemoved) 533 534 items.forEach { assertThat(it.component.packageName).isNotEqualTo(pkgToBeRemoved) } 535 } 536 537 @Test 538 fun setCarPackageManager_distractionValuesUpdated() { 539 model = createSpyDockViewModel(shouldSetCarPackageManager = false) 540 for (i in 0..<4) { 541 model.internalItems[i] = TestUtils.createAppItem( 542 pkg = "pkg$i", 543 clazz = "class$i", 544 isDrivingOptimized = false 545 ) 546 } 547 whenever(carPackageManagerMock.isActivityDistractionOptimized( 548 eq("pkg0"), 549 eq("class0") 550 )).thenReturn(true) 551 whenever(carPackageManagerMock.isActivityDistractionOptimized( 552 eq("pkg1"), 553 eq("class1") 554 )).thenReturn(false) 555 whenever(carPackageManagerMock.isActivityDistractionOptimized( 556 eq("pkg2"), 557 eq("class2") 558 )).thenReturn(true) 559 whenever(carPackageManagerMock.isActivityDistractionOptimized( 560 eq("pkg3"), 561 eq("class3") 562 )).thenReturn(false) 563 564 model.setCarPackageManager(carPackageManagerMock) 565 566 items.forEach { 567 when (it.component.packageName) { 568 "pkg0" -> assertThat(it.isDistractionOptimized).isEqualTo(true) 569 "pkg1" -> assertThat(it.isDistractionOptimized).isEqualTo(false) 570 "pkg2" -> assertThat(it.isDistractionOptimized).isEqualTo(true) 571 "pkg3" -> assertThat(it.isDistractionOptimized).isEqualTo(false) 572 } 573 } 574 } 575 576 private fun createSpyDockViewModel( 577 shouldSetCarPackageManager: Boolean = true, 578 launcherActivities: MutableSet<ComponentName> = createTestComponentList( 579 pkgPrefix = "LAUNCHER_PKG", 580 classPrefix = "LAUNCHER_CLASS" 581 ).toMutableSet(), 582 defaultPinnedItems: List<ComponentName> = createTestComponentList( 583 pkgPrefix = "DEFAULT_PKG", 584 classPrefix = "DEFAULT_CLASS" 585 ), 586 excludedComponents: Set<ComponentName> = setOf(), 587 ): DockViewModel { 588 return spy(DockViewModel( 589 maxItemsInDock = MAX_ITEMS, 590 context = contextMock, 591 packageManager = packageManagerMock, 592 if (shouldSetCarPackageManager) carPackageManagerMock else null, 593 userId = CURRENT_USER_ID, 594 launcherActivities, 595 defaultPinnedItems, 596 isPackageExcluded = { false }, 597 isComponentExcluded = { excludedComponents.contains(it) }, 598 iconFactory = iconFactoryMock, 599 dockProtoDataController = dataController, 600 observer = { items = it }, 601 )) 602 } 603 604 private fun createTestRunningTaskInfoList( 605 pkgPrefix: String = "testRunningTaskInfo_PKG", 606 classPrefix: String = "testRunningTaskInfo_CLASS", 607 component: ComponentName? = null, 608 userId: Int = CURRENT_USER_ID 609 ): List<ActivityManager.RunningTaskInfo> { 610 val recentTasks = mutableListOf<ActivityManager.RunningTaskInfo>() 611 for (i in 1..10) { 612 val t = mock<ActivityManager.RunningTaskInfo> {} 613 t.userId = userId 614 val cmp = component ?: createNewComponent(pkg = "$pkgPrefix$i", "$classPrefix$i") 615 t.baseActivity = cmp 616 val intent = mock<Intent> {} 617 intent.component = cmp 618 t.baseIntent = intent 619 recentTasks.add(t) 620 } 621 return recentTasks 622 } 623 624 private fun createTestComponentList( 625 pkgPrefix: String, 626 classPrefix: String, 627 isService: Boolean = false, 628 ): List<ComponentName> { 629 val launcherActivities = mutableListOf<ComponentName>() 630 for (i in 1..10) { 631 launcherActivities.add( 632 createNewComponent(pkg = "$pkgPrefix$i", "$classPrefix$i", isService) 633 ) 634 } 635 return launcherActivities 636 } 637 638 private fun createNewComponent( 639 pkg: String, 640 clazz: String, 641 isService: Boolean = false 642 ): ComponentName { 643 val component = ComponentName(pkg, clazz) 644 val pi = createMockPackageInfo(component, isService) 645 whenever(packageManagerMock.getPackageInfo(eq(pkg), any<PackageManager.PackageInfoFlags>())) 646 .thenReturn(pi) 647 return component 648 } 649 650 private fun createMockPackageInfo( 651 component: ComponentName, 652 isService: Boolean = false 653 ): PackageInfo { 654 val pi = mock<PackageInfo> {} 655 if (isService) { 656 pi.services = arrayOf(createMockServiceInfo(component)) 657 } else { 658 pi.activities = arrayOf(createMockActivityInfo(component)) 659 } 660 return pi 661 } 662 663 private fun createMockActivityInfo(component: ComponentName): ActivityInfo { 664 val icon = mock<Drawable> {} 665 val ai = mock<ActivityInfo> { 666 on { loadIcon(any()) } doReturn icon 667 on { loadLabel(any()) } doReturn "${component.packageName}-${component.className}" 668 on { componentName } doReturn component 669 } 670 ai.name = "${component.packageName}-${component.className}" 671 return ai 672 } 673 674 private fun createMockServiceInfo(component: ComponentName): ServiceInfo { 675 val icon = mock<Drawable> {} 676 val si = mock<ServiceInfo> { 677 on { loadIcon(any()) } doReturn icon 678 on { componentName } doReturn component 679 } 680 si.name = "${component.packageName}-${component.className}" 681 return si 682 } 683 } 684