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