2020-01-05 15:35:29 +05:30
|
|
|
/*
|
|
|
|
|
* 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.Executors.createAndStartNewForegroundLooper;
|
|
|
|
|
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
|
|
|
|
|
|
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
|
|
|
import static org.junit.Assert.assertNotNull;
|
|
|
|
|
import static org.junit.Assert.assertNull;
|
|
|
|
|
import static org.mockito.Mockito.spy;
|
|
|
|
|
import static org.robolectric.Shadows.shadowOf;
|
|
|
|
|
|
|
|
|
|
import android.os.Process;
|
|
|
|
|
|
|
|
|
|
import com.android.launcher3.PagedView;
|
|
|
|
|
import com.android.launcher3.model.BgDataModel.Callbacks;
|
2020-04-06 15:11:17 -07:00
|
|
|
import com.android.launcher3.model.data.AppInfo;
|
|
|
|
|
import com.android.launcher3.model.data.ItemInfo;
|
2020-11-05 17:07:35 -08:00
|
|
|
import com.android.launcher3.shadows.ShadowLooperExecutor;
|
2020-01-05 15:35:29 +05:30
|
|
|
import com.android.launcher3.util.Executors;
|
|
|
|
|
import com.android.launcher3.util.LauncherLayoutBuilder;
|
|
|
|
|
import com.android.launcher3.util.LauncherModelHelper;
|
|
|
|
|
import com.android.launcher3.util.LooperExecutor;
|
|
|
|
|
import com.android.launcher3.util.ViewOnDrawExecutor;
|
|
|
|
|
|
|
|
|
|
import org.junit.Before;
|
|
|
|
|
import org.junit.Test;
|
|
|
|
|
import org.junit.runner.RunWith;
|
2020-04-08 16:27:28 -07:00
|
|
|
import org.robolectric.RobolectricTestRunner;
|
2020-01-05 15:35:29 +05:30
|
|
|
import org.robolectric.RuntimeEnvironment;
|
|
|
|
|
import org.robolectric.annotation.LooperMode;
|
|
|
|
|
import org.robolectric.annotation.LooperMode.Mode;
|
2020-11-05 17:07:35 -08:00
|
|
|
import org.robolectric.shadow.api.Shadow;
|
2020-01-05 15:35:29 +05:30
|
|
|
import org.robolectric.shadows.ShadowPackageManager;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tests to verify multiple callbacks in Loader
|
|
|
|
|
*/
|
2020-04-08 16:27:28 -07:00
|
|
|
@RunWith(RobolectricTestRunner.class)
|
2020-01-05 15:35:29 +05:30
|
|
|
@LooperMode(Mode.PAUSED)
|
|
|
|
|
public class ModelMultiCallbacksTest {
|
|
|
|
|
|
|
|
|
|
private LauncherModelHelper mModelHelper;
|
|
|
|
|
|
|
|
|
|
private ShadowPackageManager mSpm;
|
|
|
|
|
private LooperExecutor mTempMainExecutor;
|
|
|
|
|
|
|
|
|
|
@Before
|
|
|
|
|
public void setUp() throws Exception {
|
|
|
|
|
mModelHelper = new LauncherModelHelper();
|
|
|
|
|
mModelHelper.installApp(TEST_PACKAGE);
|
|
|
|
|
|
|
|
|
|
mSpm = shadowOf(RuntimeEnvironment.application.getPackageManager());
|
|
|
|
|
|
|
|
|
|
// Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
|
|
|
|
|
// so that we can wait appropriately for the loader to complete.
|
|
|
|
|
mTempMainExecutor = new LooperExecutor(createAndStartNewForegroundLooper("tempMain"));
|
2020-11-05 17:07:35 -08:00
|
|
|
ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR);
|
|
|
|
|
sle.setHandler(mTempMainExecutor.getHandler());
|
2020-01-05 15:35:29 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testTwoCallbacks_loadedTogether() throws Exception {
|
|
|
|
|
setupWorkspacePages(3);
|
|
|
|
|
|
|
|
|
|
MyCallbacks cb1 = spy(MyCallbacks.class);
|
|
|
|
|
mModelHelper.getModel().addCallbacksAndLoad(cb1);
|
|
|
|
|
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifySynchronouslyBound(3);
|
|
|
|
|
|
|
|
|
|
// Add a new callback
|
|
|
|
|
cb1.reset();
|
|
|
|
|
MyCallbacks cb2 = spy(MyCallbacks.class);
|
|
|
|
|
cb2.mPageToBindSync = 2;
|
|
|
|
|
mModelHelper.getModel().addCallbacksAndLoad(cb2);
|
|
|
|
|
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifySynchronouslyBound(3);
|
|
|
|
|
cb2.verifySynchronouslyBound(3);
|
|
|
|
|
|
|
|
|
|
// Remove callbacks
|
|
|
|
|
cb1.reset();
|
|
|
|
|
cb2.reset();
|
|
|
|
|
|
|
|
|
|
// No effect on callbacks when removing an callback
|
|
|
|
|
mModelHelper.getModel().removeCallbacks(cb2);
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
assertNull(cb1.mDeferredExecutor);
|
|
|
|
|
assertNull(cb2.mDeferredExecutor);
|
|
|
|
|
|
|
|
|
|
// Reloading only loads registered callbacks
|
|
|
|
|
mModelHelper.getModel().startLoader();
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifySynchronouslyBound(3);
|
|
|
|
|
assertNull(cb2.mDeferredExecutor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testTwoCallbacks_receiveUpdates() throws Exception {
|
|
|
|
|
setupWorkspacePages(1);
|
|
|
|
|
|
|
|
|
|
MyCallbacks cb1 = spy(MyCallbacks.class);
|
|
|
|
|
MyCallbacks cb2 = spy(MyCallbacks.class);
|
|
|
|
|
mModelHelper.getModel().addCallbacksAndLoad(cb1);
|
|
|
|
|
mModelHelper.getModel().addCallbacksAndLoad(cb2);
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
|
|
|
|
|
cb1.verifyApps(TEST_PACKAGE);
|
|
|
|
|
cb2.verifyApps(TEST_PACKAGE);
|
|
|
|
|
|
|
|
|
|
// Install package 1
|
|
|
|
|
String pkg1 = "com.test.pkg1";
|
|
|
|
|
mModelHelper.installApp(pkg1);
|
|
|
|
|
mModelHelper.getModel().onPackageAdded(pkg1, Process.myUserHandle());
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifyApps(TEST_PACKAGE, pkg1);
|
|
|
|
|
cb2.verifyApps(TEST_PACKAGE, pkg1);
|
|
|
|
|
|
|
|
|
|
// Install package 2
|
|
|
|
|
String pkg2 = "com.test.pkg2";
|
|
|
|
|
mModelHelper.installApp(pkg2);
|
|
|
|
|
mModelHelper.getModel().onPackageAdded(pkg2, Process.myUserHandle());
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifyApps(TEST_PACKAGE, pkg1, pkg2);
|
|
|
|
|
cb2.verifyApps(TEST_PACKAGE, pkg1, pkg2);
|
|
|
|
|
|
|
|
|
|
// Uninstall package 2
|
|
|
|
|
mSpm.removePackage(pkg1);
|
|
|
|
|
mModelHelper.getModel().onPackageRemoved(pkg1, Process.myUserHandle());
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifyApps(TEST_PACKAGE, pkg2);
|
|
|
|
|
cb2.verifyApps(TEST_PACKAGE, pkg2);
|
|
|
|
|
|
|
|
|
|
// Unregister a callback and verify updates no longer received
|
|
|
|
|
mModelHelper.getModel().removeCallbacks(cb2);
|
|
|
|
|
mSpm.removePackage(pkg2);
|
|
|
|
|
mModelHelper.getModel().onPackageRemoved(pkg2, Process.myUserHandle());
|
|
|
|
|
waitForLoaderAndTempMainThread();
|
|
|
|
|
cb1.verifyApps(TEST_PACKAGE);
|
|
|
|
|
cb2.verifyApps(TEST_PACKAGE, pkg2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void waitForLoaderAndTempMainThread() throws Exception {
|
|
|
|
|
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
|
|
|
|
|
mTempMainExecutor.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<>();
|
|
|
|
|
int mPageToBindSync = 0;
|
|
|
|
|
int mPageBoundSync = PagedView.INVALID_PAGE;
|
|
|
|
|
ViewOnDrawExecutor mDeferredExecutor;
|
|
|
|
|
AppInfo[] mAppInfos;
|
|
|
|
|
|
|
|
|
|
MyCallbacks() { }
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onPageBoundSynchronously(int page) {
|
|
|
|
|
mPageBoundSync = page;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
|
|
|
|
|
mDeferredExecutor = executor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
|
|
|
|
|
mItems.addAll(shortcuts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
2020-06-08 17:06:09 -07:00
|
|
|
public void bindAllApplications(AppInfo[] apps, int flags) {
|
2020-01-05 15:35:29 +05:30
|
|
|
mAppInfos = apps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getPageToBindSynchronously() {
|
|
|
|
|
return mPageToBindSync;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void reset() {
|
|
|
|
|
mItems.clear();
|
|
|
|
|
mPageBoundSync = PagedView.INVALID_PAGE;
|
|
|
|
|
mDeferredExecutor = null;
|
|
|
|
|
mAppInfos = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void verifySynchronouslyBound(int totalItems) {
|
|
|
|
|
// Verify that the requested page is bound synchronously
|
|
|
|
|
assertEquals(mPageBoundSync, mPageToBindSync);
|
|
|
|
|
assertEquals(mItems.size(), 1);
|
|
|
|
|
assertEquals(mItems.get(0).screenId, mPageBoundSync);
|
|
|
|
|
assertNotNull(mDeferredExecutor);
|
|
|
|
|
|
|
|
|
|
// Verify that all other pages are bound properly
|
|
|
|
|
mDeferredExecutor.runAllTasks();
|
|
|
|
|
assertEquals(mItems.size(), totalItems);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void verifyApps(String... apps) {
|
|
|
|
|
assertEquals(apps.length, mAppInfos.length);
|
|
|
|
|
assertEquals(Arrays.stream(mAppInfos)
|
|
|
|
|
.map(ai -> ai.getTargetComponent().getPackageName())
|
|
|
|
|
.collect(Collectors.toSet()),
|
|
|
|
|
new HashSet<>(Arrays.asList(apps)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|