1 /* 2 * Copyright (C) 2022 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.launcher3.model 18 19 import android.util.Pair 20 import androidx.test.ext.junit.runners.AndroidJUnit4 21 import androidx.test.filters.SmallTest 22 import com.android.launcher3.model.data.ItemInfo 23 import com.android.launcher3.model.data.WorkspaceItemInfo 24 import com.android.launcher3.util.Executors 25 import com.android.launcher3.util.IntArray 26 import com.android.launcher3.util.TestUtil.runOnExecutorSync 27 import com.android.launcher3.util.any 28 import com.android.launcher3.util.eq 29 import com.android.launcher3.util.same 30 import com.google.common.truth.Truth.assertThat 31 import org.junit.After 32 import org.junit.Before 33 import org.junit.Test 34 import org.junit.runner.RunWith 35 import org.mockito.Mock 36 import org.mockito.Mockito.times 37 import org.mockito.Mockito.verify 38 import org.mockito.Mockito.verifyZeroInteractions 39 import org.mockito.Mockito.`when` as whenever 40 import org.mockito.MockitoAnnotations 41 42 /** Tests for [AddWorkspaceItemsTask] */ 43 @SmallTest 44 @RunWith(AndroidJUnit4::class) 45 class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { 46 47 private lateinit var mDataModelCallbacks: MyCallbacks 48 49 @Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder 50 51 @Before setupnull52 override fun setup() { 53 super.setup() 54 MockitoAnnotations.initMocks(this) 55 mDataModelCallbacks = MyCallbacks() 56 Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) } 57 .get() 58 } 59 60 @After tearDownnull61 override fun tearDown() { 62 super.tearDown() 63 } 64 65 @Test givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItemnull66 fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() { 67 val itemToAdd = getNewItem() 68 val nonEmptyScreenIds = listOf(0, 1, 2) 69 givenNewItemSpaces(NewItemSpace(1, 2, 2)) 70 71 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 72 73 assertThat(addedItems.size).isEqualTo(1) 74 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) 75 assertThat(addedItems.first().isAnimated).isTrue() 76 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 77 } 78 79 @Test givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItemnull80 fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() { 81 val itemsToAdd = arrayOf(getNewItem(), getExistingItem()) 82 givenNewItemSpaces(NewItemSpace(1, 0, 0)) 83 val nonEmptyScreenIds = listOf(0) 84 85 val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) 86 87 assertThat(addedItems.size).isEqualTo(1) 88 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) 89 assertThat(addedItems.first().isAnimated).isTrue() 90 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 91 } 92 93 @Test givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItemnull94 fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() { 95 val itemToAdd = getExistingItem() 96 givenNewItemSpaces(NewItemSpace(1, 0, 0)) 97 val nonEmptyScreenIds = listOf(0) 98 99 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 100 101 assertThat(addedItems.size).isEqualTo(0) 102 verifyZeroInteractions(mWorkspaceItemSpaceFinder) 103 } 104 105 @Test givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenIdnull106 fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() { 107 val itemToAdd = getNewItem() 108 givenNewItemSpaces(NewItemSpace(2, 1, 3)) 109 val nonEmptyScreenIds = listOf(0, 2, 3) 110 111 val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) 112 113 assertThat(addedItems.size).isEqualTo(1) 114 assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2) 115 assertThat(addedItems.first().isAnimated).isTrue() 116 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) 117 } 118 119 @Test givenMultipleItems_whenExecuteTask_thenAddThemnull120 fun givenMultipleItems_whenExecuteTask_thenAddThem() { 121 val itemsToAdd = 122 arrayOf( 123 getNewItem(), 124 getExistingItem(), 125 getNewItem(), 126 getNewItem(), 127 getExistingItem(), 128 ) 129 givenNewItemSpaces( 130 NewItemSpace(1, 3, 3), 131 NewItemSpace(2, 0, 0), 132 NewItemSpace(2, 0, 1), 133 ) 134 val nonEmptyScreenIds = listOf(0, 1) 135 136 val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) 137 138 // Only the new items should be added 139 assertThat(addedItems.size).isEqualTo(3) 140 141 // Items that are added to the first screen should not be animated 142 val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 } 143 assertThat(itemsAddedToFirstScreen.size).isEqualTo(1) 144 assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse() 145 146 // Items that are added to the second screen should be animated 147 val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 } 148 assertThat(itemsAddedToSecondScreen.size).isEqualTo(2) 149 itemsAddedToSecondScreen.forEach { assertThat(it.isAnimated).isTrue() } 150 verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3) 151 } 152 153 /** Sets up the item space data that will be returned from WorkspaceItemSpaceFinder. */ givenNewItemSpacesnull154 private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) { 155 val spaceStack = newItemSpaces.toMutableList() 156 whenever( 157 mWorkspaceItemSpaceFinder.findSpaceForItem(any(), any(), any(), any(), any(), any()) 158 ) 159 .then { spaceStack.removeFirst().toIntArray() } 160 } 161 162 /** 163 * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was 164 * it called. 165 */ verifyItemSpaceFinderCallnull166 private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List<Int>, numberOfExpectedCall: Int) { 167 verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall)) 168 .findSpaceForItem( 169 same(mAppState), 170 same(mModelHelper.bgDataModel), 171 eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), 172 eq(IntArray()), 173 eq(1), 174 eq(1) 175 ) 176 } 177 178 /** 179 * Sets up the workspaces with items, executes the task, collects the added items from the model 180 * callback then returns it. 181 */ testAddItemsnull182 private fun testAddItems( 183 nonEmptyScreenIds: List<Int>, 184 vararg itemsToAdd: WorkspaceItemInfo 185 ): List<AddedItem> { 186 setupWorkspaces(nonEmptyScreenIds) 187 val task = newTask(*itemsToAdd) 188 189 val addedItems = mutableListOf<AddedItem>() 190 191 runOnExecutorSync(Executors.MODEL_EXECUTOR) { 192 mDataModelCallbacks.addedItems.clear() 193 mModelHelper.model.enqueueModelUpdateTask(task) 194 runOnExecutorSync(Executors.MAIN_EXECUTOR) {} 195 addedItems.addAll(mDataModelCallbacks.addedItems) 196 } 197 198 return addedItems 199 } 200 201 /** 202 * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency 203 * with a mock. 204 */ newTasknull205 private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = 206 items 207 .map { Pair.create(it, Any()) } 208 .toMutableList() <lambda>null209 .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) } 210 } 211 212 private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) 213 214 private class MyCallbacks : BgDataModel.Callbacks { 215 216 val addedItems = mutableListOf<AddedItem>() 217 bindAppsAddednull218 override fun bindAppsAdded( 219 newScreens: IntArray?, 220 addNotAnimated: ArrayList<ItemInfo>, 221 addAnimated: ArrayList<ItemInfo> 222 ) { 223 addedItems.addAll(addAnimated.map { AddedItem(it, true) }) 224 addedItems.addAll(addNotAnimated.map { AddedItem(it, false) }) 225 } 226 } 227