Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e77eafc2 authored by Andras Kloczl's avatar Andras Kloczl
Browse files

Migrate workspace item adding tests to kotlin

In a former CL (http://ag/16064580) I've tried to
do the same but presubmit was constantly failing,
probably because the test is in kotlin.

Test: AddWorkspaceItemsTaskTest.kt
Bug: 199160559
Change-Id: Ie1bc4fcd4f94cd7cb0601c21bbdf273452b9dd1f
parent 3c229866
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ package {
// Source code used for test
filegroup {
    name: "launcher-tests-src",
    srcs: ["src/**/*.java"],
    srcs: ["src/**/*.java", "src/**/*.kt"],
}

// Source code used for oop test helpers
+0 −201
Original line number Diff line number Diff line
package com.android.launcher3.model;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.util.Pair;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.LauncherModelHelper;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

import java.util.ArrayList;
import java.util.List;

/**
 * Tests for {@link AddWorkspaceItemsTask}
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AddWorkspaceItemsTaskTest {

    private final ComponentName mComponent1 = new ComponentName("a", "b");
    private final ComponentName mComponent2 = new ComponentName("b", "b");

    private Context mTargetContext;
    private InvariantDeviceProfile mIdp;
    private LauncherAppState mAppState;
    private LauncherModelHelper mModelHelper;

    private IntArray mExistingScreens;
    private IntArray mNewScreens;
    private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;

    @Before
    public void setup() {
        mModelHelper = new LauncherModelHelper();
        mTargetContext = mModelHelper.sandboxContext;
        mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
        mIdp.numColumns = mIdp.numRows = 5;
        mAppState = LauncherAppState.getInstance(mTargetContext);

        mExistingScreens = new IntArray();
        mScreenOccupancy = new IntSparseArrayMap<>();
        mNewScreens = new IntArray();
    }

    @After
    public void tearDown() {
        mModelHelper.destroy();
    }

    private AddWorkspaceItemsTask newTask(ItemInfo... items) {
        List<Pair<ItemInfo, Object>> list = new ArrayList<>();
        for (ItemInfo item : items) {
            list.add(Pair.create(item, null));
        }
        return new AddWorkspaceItemsTask(list);
    }

    @Test
    public void testFindSpaceForItem_prefers_second() throws Exception {
        // First screen has only one hole of size 1
        int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));

        // Second screen has 2 holes of sizes 3x2 and 2x3
        setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));

        int[] spaceFound = newTask().findSpaceForItem(
                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
        assertEquals(1, spaceFound[0]);
        assertTrue(mScreenOccupancy.get(spaceFound[0])
                .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));

        // Find a larger space
        spaceFound = newTask().findSpaceForItem(
                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
        assertEquals(2, spaceFound[0]);
        assertTrue(mScreenOccupancy.get(spaceFound[0])
                .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
    }

    @Test
    public void testFindSpaceForItem_adds_new_screen() throws Exception {
        // First screen has 2 holes of sizes 3x2 and 2x3
        setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));

        IntArray oldScreens = mExistingScreens.clone();
        int[] spaceFound = newTask().findSpaceForItem(
                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
        assertFalse(oldScreens.contains(spaceFound[0]));
        assertTrue(mNewScreens.contains(spaceFound[0]));
    }

    @Test
    public void testAddItem_existing_item_ignored() throws Exception {
        WorkspaceItemInfo info = new WorkspaceItemInfo();
        info.intent = new Intent().setComponent(mComponent1);

        // Setup a screen with a hole
        setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));

        // Nothing was added
        assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
    }

    @Test
    public void testAddItem_some_items_added() throws Exception {
        Callbacks callbacks = mock(Callbacks.class);
        Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();

        WorkspaceItemInfo info = new WorkspaceItemInfo();
        info.intent = new Intent().setComponent(mComponent1);

        WorkspaceItemInfo info2 = new WorkspaceItemInfo();
        info2.intent = new Intent().setComponent(mComponent2);

        // Setup a screen with a hole
        setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));

        mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
        ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
        ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);

        // only info2 should be added because info was already added to the workspace
        // in setupWorkspaceWithHoles()
        verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
                animated.capture());
        assertTrue(notAnimated.getValue().isEmpty());

        assertEquals(1, animated.getValue().size());
        assertTrue(animated.getValue().contains(info2));
    }

    private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
        return mModelHelper.executeSimpleTask(
                model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
    }

    private int writeWorkspaceWithHoles(
            BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
        GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
        occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
        for (Rect r : holes) {
            occupancy.markCells(r, false);
        }

        mExistingScreens.add(screenId);
        mScreenOccupancy.append(screenId, occupancy);

        for (int x = 0; x < mIdp.numColumns; x++) {
            for (int y = 0; y < mIdp.numRows; y++) {
                if (!occupancy.cells[x][y]) {
                    continue;
                }

                WorkspaceItemInfo info = new WorkspaceItemInfo();
                info.intent = new Intent().setComponent(mComponent1);
                info.id = startId++;
                info.screenId = screenId;
                info.cellX = x;
                info.cellY = y;
                info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
                bgDataModel.addItem(mTargetContext, info, false);

                ContentWriter writer = new ContentWriter(mTargetContext);
                info.writeToValues(writer);
                writer.put(Favorites._ID, info.id);
                mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
                        writer.getValues(mTargetContext));
            }
        }
        return startId;
    }
}
+325 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.model

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.util.Pair
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.*
import com.android.launcher3.util.IntArray
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.*
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.verify
import kotlin.collections.ArrayList

/**
 * Tests for [AddWorkspaceItemsTask]
 */
@SmallTest
@RunWith(AndroidJUnit4::class)
class AddWorkspaceItemsTaskTest {

    @Captor
    private lateinit var animatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>

    @Captor
    private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>

    @Mock
    private lateinit var dataModelCallbacks: BgDataModel.Callbacks

    private lateinit var mTargetContext: Context
    private lateinit var mIdp: InvariantDeviceProfile
    private lateinit var mAppState: LauncherAppState
    private lateinit var mModelHelper: LauncherModelHelper
    private lateinit var mExistingScreens: IntArray
    private lateinit var mNewScreens: IntArray
    private lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>

    private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5))
    private val fullScreenHoles = emptyList<Rect>()


    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        mModelHelper = LauncherModelHelper()
        mTargetContext = mModelHelper.sandboxContext
        mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
        mIdp.numRows = 5
        mIdp.numColumns = mIdp.numRows
        mAppState = LauncherAppState.getInstance(mTargetContext)
        mExistingScreens = IntArray()
        mScreenOccupancy = IntSparseArrayMap()
        mNewScreens = IntArray()
        Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(dataModelCallbacks) }.get()
    }

    @After
    fun tearDown() {
        mModelHelper.destroy()
    }

    @Test
    fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() {
        setupWorkspacesWithHoles(
                screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
                //  2 holes of sizes 3x2 and 2x3
                screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
        )

        val spaceFound = newTask().findSpaceForItem(
                mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 1, 1)
        assertEquals(1, spaceFound[0])
        assertTrue(mScreenOccupancy[spaceFound[0]]
                .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1))
    }

    @Test
    fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() {
        setupWorkspacesWithHoles(
                screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
                //  2 holes of sizes 3x2 and 2x3
                screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
        )

        // Find a larger space
        val spaceFound = newTask().findSpaceForItem(
                mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 2, 3)
        assertEquals(2, spaceFound[0])
        assertTrue(mScreenOccupancy[spaceFound[0]]
                .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3))
    }

    @Test
    fun notEnoughSpaceOnExistingScreens_whenFindSpaceForItem_thenReturnNewScreenId() {
        setupWorkspacesWithHoles(
                //  2 holes of sizes 3x2 and 2x3
                screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
                //  2 holes of sizes 1x2 and 2x2
                screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)),
        )

        val oldScreens = mExistingScreens.clone()
        val spaceFound = newTask().findSpaceForItem(
                mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 3, 3)
        assertFalse(oldScreens.contains(spaceFound[0]))
        assertTrue(mNewScreens.contains(spaceFound[0]))
    }

    @Test
    fun enoughSpaceOnFirstScreen_whenTaskRuns_thenAddItemToFirstScreen() {
        val workspaceHoles = createWorkspaceHoles(
                screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
                screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
        )
        val addedItems = testAddItems(workspaceHoles, getNewItem())
        assertEquals(1, addedItems.size)
        assertEquals(1, addedItems.first().itemInfo.screenId)
    }

    @Test
    fun firstPageIsFull_whenTaskRuns_thenAddItemToSecondScreen() {
        val workspaceHoles = createWorkspaceHoles(
                screen1 = fullScreenHoles,
        )
        val addedItems = testAddItems(workspaceHoles, getNewItem())
        assertEquals(1, addedItems.size)
        assertEquals(2, addedItems.first().itemInfo.screenId)
    }

    @Test
    fun firstScreenIsEmptyButSecondIsNotEmpty_whenTaskRuns_thenAddItemToSecondScreen() {
        val workspaceHoles = createWorkspaceHoles(
                screen1 = emptyScreenHoles,
                screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
        )
        val addedItems = testAddItems(workspaceHoles, getNewItem())
        assertEquals(1, addedItems.size)
        assertEquals(2, addedItems.first().itemInfo.screenId)
    }

    @Test
    fun twoEmptyMiddleScreens_whenTaskRuns_thenAddItemToThirdScreen() {
        val workspaceHoles = createWorkspaceHoles(
                screen1 = emptyScreenHoles,
                screen2 = emptyScreenHoles,
                screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space
        )
        val addedItems = testAddItems(workspaceHoles, getNewItem())
        assertEquals(1, addedItems.size)
        assertEquals(3, addedItems.first().itemInfo.screenId)
    }

    @Test
    fun allPagesAreFull_whenTaskRuns_thenAddItemToNewScreen() {
        val workspaceHoles = createWorkspaceHoles(
                screen1 = fullScreenHoles,
                screen2 = fullScreenHoles,
        )
        val addedItems = testAddItems(workspaceHoles, getNewItem())
        assertEquals(1, addedItems.size)
        assertEquals(3, addedItems.first().itemInfo.screenId)
    }

    @Test
    fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_whenTaskRuns_thenAddItemToThirdPage() {
        val workspaceHoles = createWorkspaceHoles(
                screen1 = fullScreenHoles,
                screen2 = fullScreenHoles,
                screen3 = emptyScreenHoles
        )
        val addedItems = testAddItems(workspaceHoles, getNewItem())
        assertEquals(1, addedItems.size)
        assertEquals(3, addedItems.first().itemInfo.screenId)
    }

    @Test
    fun itemIsAlreadyAdded_whenTaskRun_thenIgnoreItem() {
        val task = newTask(getExistingItem())
        setupWorkspacesWithHoles(
                screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
        )

        // Nothing was added
        assertTrue(mModelHelper.executeTaskForTest(task).isEmpty())
    }

    @Test
    fun newAndExistingItems_whenTaskRun_thenAddOnlyTheNewOne() {
        val newItem = getNewItem()
        val workspaceHoles = createWorkspaceHoles(
                screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
        )
        val addedItems = testAddItems(workspaceHoles, getExistingItem(), newItem)
        assertEquals(1, addedItems.size)
        val addedItem = addedItems.first()
        assert(addedItem.isAnimated)
        val addedItemInfo = addedItem.itemInfo
        assertEquals(1, addedItemInfo.screenId)
        assertEquals(newItem, addedItemInfo)
    }

    private fun testAddItems(
            workspaceHoles: List<List<Rect>>,
            vararg itemsToAdd: WorkspaceItemInfo
    ): List<AddedItem> {
        setupWorkspaces(workspaceHoles)
        mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run()

        verify(dataModelCallbacks).bindAppsAdded(any(),
                notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture())

        val addedItems = mutableListOf<AddedItem>()
        addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) })
        addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
        return addedItems
    }

    private fun setupWorkspaces(workspaceHoles: List<List<Rect>>) {
        var nextItemId = 1
        var screenId = 1
        workspaceHoles.forEach { holes ->
            nextItemId = setupWorkspace(nextItemId, screenId++, *holes.toTypedArray())
        }
    }

    private fun setupWorkspace(startId: Int, screenId: Int, vararg holes: Rect): Int {
        return mModelHelper.executeSimpleTask { dataModel ->
            writeWorkspaceWithHoles(dataModel, startId, screenId, *holes)
        }
    }

    private fun writeWorkspaceWithHoles(
            bgDataModel: BgDataModel,
            itemStartId: Int,
            screenId: Int,
            vararg holes: Rect,
    ): Int {
        var itemId = itemStartId
        val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
        occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
        holes.forEach { holeRect ->
            occupancy.markCells(holeRect, false)
        }
        mExistingScreens.add(screenId)
        mScreenOccupancy.append(screenId, occupancy)
        for (x in 0 until mIdp.numColumns) {
            for (y in 0 until mIdp.numRows) {
                if (!occupancy.cells[x][y]) {
                    continue
                }
                val info = getExistingItem()
                info.id = itemId++
                info.screenId = screenId
                info.cellX = x
                info.cellY = y
                info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
                bgDataModel.addItem(mTargetContext, info, false)
                val writer = ContentWriter(mTargetContext)
                info.writeToValues(writer)
                writer.put(LauncherSettings.Favorites._ID, info.id)
                mTargetContext.contentResolver.insert(LauncherSettings.Favorites.CONTENT_URI,
                        writer.getValues(mTargetContext))
            }
        }
        return itemId
    }

    private fun setupWorkspacesWithHoles(
            screen1: List<Rect>? = null,
            screen2: List<Rect>? = null,
            screen3: List<Rect>? = null,
    ) = createWorkspaceHoles(screen1, screen2, screen3)
            .let(this::setupWorkspaces)

    private fun createWorkspaceHoles(
            screen1: List<Rect>? = null,
            screen2: List<Rect>? = null,
            screen3: List<Rect>? = null,
    ): List<List<Rect>> = listOfNotNull(screen1, screen2, screen3)

    private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask =
            items.map { Pair.create(it, Any()) }
                    .toMutableList()
                    .let(::AddWorkspaceItemsTask)

    private fun getExistingItem() = WorkspaceItemInfo()
            .apply { intent = Intent().setComponent(ComponentName("a", "b")) }

    private fun getNewItem() = WorkspaceItemInfo()
            .apply { intent = Intent().setComponent(ComponentName("b", "b")) }
}

private data class AddedItem(
        val itemInfo: ItemInfo,
        val isAnimated: Boolean
)
 No newline at end of file