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