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

Commit 95b43e59 authored by András Klöczl's avatar András Klöczl Committed by Automerger Merge Worker
Browse files

Merge "Migrate workspace item adding tests to kotlin" into sc-v2-dev am: 283798db am: 2d1cfb89

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/16321893

Change-Id: I1bbd1d40c9f4f69d9c4d6123229dfdf71a3ba849
parents 2685dba8 2d1cfb89
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