blob: 42c9f11a528b873d9e52db89a128f380520bfe4d [file] [log] [blame]
/*
* Copyright (C) 2020 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 static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import android.os.Process;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.TestUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Tests to verify multiple callbacks in Loader
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ModelMultiCallbacksTest {
private LauncherModelHelper mModelHelper;
@Before
public void setUp() {
mModelHelper = new LauncherModelHelper();
}
@After
public void tearDown() throws Exception {
mModelHelper.destroy();
TestUtil.uninstallDummyApp();
}
@Test
public void testTwoCallbacks_loadedTogether() throws Exception {
setupWorkspacePages(3);
MyCallbacks cb1 = spy(MyCallbacks.class);
Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1));
waitForLoaderAndTempMainThread();
cb1.verifySynchronouslyBound(3);
// Add a new callback
cb1.reset();
MyCallbacks cb2 = spy(MyCallbacks.class);
cb2.mPageToBindSync = IntSet.wrap(2);
Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2));
waitForLoaderAndTempMainThread();
assertFalse(cb1.bindStarted);
cb2.verifySynchronouslyBound(3);
// Remove callbacks
cb1.reset();
cb2.reset();
// No effect on callbacks when removing an callback
Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2));
waitForLoaderAndTempMainThread();
assertNull(cb1.mPendingTasks);
assertNull(cb2.mPendingTasks);
// Reloading only loads registered callbacks
mModelHelper.getModel().startLoader();
waitForLoaderAndTempMainThread();
cb1.verifySynchronouslyBound(3);
assertNull(cb2.mPendingTasks);
}
@Test
public void testTwoCallbacks_receiveUpdates() throws Exception {
TestUtil.uninstallDummyApp();
setupWorkspacePages(1);
MyCallbacks cb1 = spy(MyCallbacks.class);
MyCallbacks cb2 = spy(MyCallbacks.class);
Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1));
Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2));
waitForLoaderAndTempMainThread();
assertTrue(cb1.allApps().contains(TEST_PACKAGE));
assertTrue(cb2.allApps().contains(TEST_PACKAGE));
// Install package 1
TestUtil.installDummyApp();
mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle());
waitForLoaderAndTempMainThread();
assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE));
assertTrue(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE));
// Uninstall package 2
TestUtil.uninstallDummyApp();
mModelHelper.getModel().onPackageRemoved(TestUtil.DUMMY_PACKAGE, Process.myUserHandle());
waitForLoaderAndTempMainThread();
assertFalse(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE));
assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE));
// Unregister a callback and verify updates no longer received
Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2));
TestUtil.installDummyApp();
mModelHelper.getModel().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle());
waitForLoaderAndTempMainThread();
// cb2 didn't get the update
assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE));
assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE));
}
private void waitForLoaderAndTempMainThread() throws Exception {
Executors.MAIN_EXECUTOR.submit(() -> { }).get();
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
Executors.MAIN_EXECUTOR.submit(() -> { }).get();
}
private void setupWorkspacePages(int pageCount) throws Exception {
// Create a layout with 3 pages
LauncherLayoutBuilder builder = new LauncherLayoutBuilder();
for (int i = 0; i < pageCount; i++) {
builder.atWorkspace(1, 1, i).putApp(TEST_PACKAGE, TEST_PACKAGE);
}
mModelHelper.setupDefaultLayoutProvider(builder);
}
private abstract static class MyCallbacks implements Callbacks {
final List<ItemInfo> mItems = new ArrayList<>();
IntSet mPageToBindSync = IntSet.wrap(0);
IntSet mPageBoundSync = new IntSet();
RunnableList mPendingTasks;
AppInfo[] mAppInfos;
boolean bindStarted;
MyCallbacks() { }
@Override
public void startBinding() {
bindStarted = true;
}
@Override
public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
mPageBoundSync = boundPages;
mPendingTasks = pendingTasks;
}
@Override
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
mItems.addAll(shortcuts);
}
@Override
public void bindAllApplications(AppInfo[] apps, int flags) {
mAppInfos = apps;
}
@Override
public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
return mPageToBindSync;
}
public void reset() {
mItems.clear();
mPageBoundSync = new IntSet();
mPendingTasks = null;
mAppInfos = null;
bindStarted = false;
}
public void verifySynchronouslyBound(int totalItems) {
// Verify that the requested page is bound synchronously
assertTrue(bindStarted);
assertEquals(mPageToBindSync, mPageBoundSync);
assertEquals(mItems.size(), 1);
assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync);
assertNotNull(mPendingTasks);
// Verify that all other pages are bound properly
mPendingTasks.executeAllAndDestroy();
assertEquals(mItems.size(), totalItems);
}
public Set<String> allApps() {
return Arrays.stream(mAppInfos)
.map(ai -> ai.getTargetComponent().getPackageName())
.collect(Collectors.toSet());
}
public void verifyApps(String... apps) {
assertTrue(allApps().containsAll(Arrays.asList(apps)));
}
}
}