1 /* 2 * Copyright (C) 2020 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 package com.android.launcher3.model; 17 18 import static com.android.launcher3.util.Executors.createAndStartNewForegroundLooper; 19 import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertNotNull; 23 import static org.junit.Assert.assertNull; 24 import static org.mockito.Mockito.spy; 25 import static org.robolectric.Shadows.shadowOf; 26 27 import android.os.Process; 28 29 import com.android.launcher3.PagedView; 30 import com.android.launcher3.model.BgDataModel.Callbacks; 31 import com.android.launcher3.model.data.AppInfo; 32 import com.android.launcher3.model.data.ItemInfo; 33 import com.android.launcher3.util.Executors; 34 import com.android.launcher3.util.LauncherLayoutBuilder; 35 import com.android.launcher3.util.LauncherModelHelper; 36 import com.android.launcher3.util.LooperExecutor; 37 import com.android.launcher3.util.ViewOnDrawExecutor; 38 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 import org.robolectric.RobolectricTestRunner; 43 import org.robolectric.RuntimeEnvironment; 44 import org.robolectric.annotation.LooperMode; 45 import org.robolectric.annotation.LooperMode.Mode; 46 import org.robolectric.shadows.ShadowPackageManager; 47 import org.robolectric.util.ReflectionHelpers; 48 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.stream.Collectors; 54 55 /** 56 * Tests to verify multiple callbacks in Loader 57 */ 58 @RunWith(RobolectricTestRunner.class) 59 @LooperMode(Mode.PAUSED) 60 public class ModelMultiCallbacksTest { 61 62 private LauncherModelHelper mModelHelper; 63 64 private ShadowPackageManager mSpm; 65 private LooperExecutor mTempMainExecutor; 66 67 @Before setUp()68 public void setUp() throws Exception { 69 mModelHelper = new LauncherModelHelper(); 70 mModelHelper.installApp(TEST_PACKAGE); 71 72 mSpm = shadowOf(RuntimeEnvironment.application.getPackageManager()); 73 74 // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread, 75 // so that we can wait appropriately for the loader to complete. 76 mTempMainExecutor = new LooperExecutor(createAndStartNewForegroundLooper("tempMain")); 77 ReflectionHelpers.setField(mModelHelper.getModel(), "mMainExecutor", mTempMainExecutor); 78 } 79 80 @Test testTwoCallbacks_loadedTogether()81 public void testTwoCallbacks_loadedTogether() throws Exception { 82 setupWorkspacePages(3); 83 84 MyCallbacks cb1 = spy(MyCallbacks.class); 85 mModelHelper.getModel().addCallbacksAndLoad(cb1); 86 87 waitForLoaderAndTempMainThread(); 88 cb1.verifySynchronouslyBound(3); 89 90 // Add a new callback 91 cb1.reset(); 92 MyCallbacks cb2 = spy(MyCallbacks.class); 93 cb2.mPageToBindSync = 2; 94 mModelHelper.getModel().addCallbacksAndLoad(cb2); 95 96 waitForLoaderAndTempMainThread(); 97 cb1.verifySynchronouslyBound(3); 98 cb2.verifySynchronouslyBound(3); 99 100 // Remove callbacks 101 cb1.reset(); 102 cb2.reset(); 103 104 // No effect on callbacks when removing an callback 105 mModelHelper.getModel().removeCallbacks(cb2); 106 waitForLoaderAndTempMainThread(); 107 assertNull(cb1.mDeferredExecutor); 108 assertNull(cb2.mDeferredExecutor); 109 110 // Reloading only loads registered callbacks 111 mModelHelper.getModel().startLoader(); 112 waitForLoaderAndTempMainThread(); 113 cb1.verifySynchronouslyBound(3); 114 assertNull(cb2.mDeferredExecutor); 115 } 116 117 @Test testTwoCallbacks_receiveUpdates()118 public void testTwoCallbacks_receiveUpdates() throws Exception { 119 setupWorkspacePages(1); 120 121 MyCallbacks cb1 = spy(MyCallbacks.class); 122 MyCallbacks cb2 = spy(MyCallbacks.class); 123 mModelHelper.getModel().addCallbacksAndLoad(cb1); 124 mModelHelper.getModel().addCallbacksAndLoad(cb2); 125 waitForLoaderAndTempMainThread(); 126 127 cb1.verifyApps(TEST_PACKAGE); 128 cb2.verifyApps(TEST_PACKAGE); 129 130 // Install package 1 131 String pkg1 = "com.test.pkg1"; 132 mModelHelper.installApp(pkg1); 133 mModelHelper.getModel().onPackageAdded(pkg1, Process.myUserHandle()); 134 waitForLoaderAndTempMainThread(); 135 cb1.verifyApps(TEST_PACKAGE, pkg1); 136 cb2.verifyApps(TEST_PACKAGE, pkg1); 137 138 // Install package 2 139 String pkg2 = "com.test.pkg2"; 140 mModelHelper.installApp(pkg2); 141 mModelHelper.getModel().onPackageAdded(pkg2, Process.myUserHandle()); 142 waitForLoaderAndTempMainThread(); 143 cb1.verifyApps(TEST_PACKAGE, pkg1, pkg2); 144 cb2.verifyApps(TEST_PACKAGE, pkg1, pkg2); 145 146 // Uninstall package 2 147 mSpm.removePackage(pkg1); 148 mModelHelper.getModel().onPackageRemoved(pkg1, Process.myUserHandle()); 149 waitForLoaderAndTempMainThread(); 150 cb1.verifyApps(TEST_PACKAGE, pkg2); 151 cb2.verifyApps(TEST_PACKAGE, pkg2); 152 153 // Unregister a callback and verify updates no longer received 154 mModelHelper.getModel().removeCallbacks(cb2); 155 mSpm.removePackage(pkg2); 156 mModelHelper.getModel().onPackageRemoved(pkg2, Process.myUserHandle()); 157 waitForLoaderAndTempMainThread(); 158 cb1.verifyApps(TEST_PACKAGE); 159 cb2.verifyApps(TEST_PACKAGE, pkg2); 160 } 161 waitForLoaderAndTempMainThread()162 private void waitForLoaderAndTempMainThread() throws Exception { 163 Executors.MODEL_EXECUTOR.submit(() -> { }).get(); 164 mTempMainExecutor.submit(() -> { }).get(); 165 } 166 setupWorkspacePages(int pageCount)167 private void setupWorkspacePages(int pageCount) throws Exception { 168 // Create a layout with 3 pages 169 LauncherLayoutBuilder builder = new LauncherLayoutBuilder(); 170 for (int i = 0; i < pageCount; i++) { 171 builder.atWorkspace(1, 1, i).putApp(TEST_PACKAGE, TEST_PACKAGE); 172 } 173 mModelHelper.setupDefaultLayoutProvider(builder); 174 } 175 176 private abstract static class MyCallbacks implements Callbacks { 177 178 final List<ItemInfo> mItems = new ArrayList<>(); 179 int mPageToBindSync = 0; 180 int mPageBoundSync = PagedView.INVALID_PAGE; 181 ViewOnDrawExecutor mDeferredExecutor; 182 AppInfo[] mAppInfos; 183 MyCallbacks()184 MyCallbacks() { } 185 186 @Override onPageBoundSynchronously(int page)187 public void onPageBoundSynchronously(int page) { 188 mPageBoundSync = page; 189 } 190 191 @Override executeOnNextDraw(ViewOnDrawExecutor executor)192 public void executeOnNextDraw(ViewOnDrawExecutor executor) { 193 mDeferredExecutor = executor; 194 } 195 196 @Override bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)197 public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { 198 mItems.addAll(shortcuts); 199 } 200 201 @Override bindAllApplications(AppInfo[] apps, int flags)202 public void bindAllApplications(AppInfo[] apps, int flags) { 203 mAppInfos = apps; 204 } 205 206 @Override getPageToBindSynchronously()207 public int getPageToBindSynchronously() { 208 return mPageToBindSync; 209 } 210 reset()211 public void reset() { 212 mItems.clear(); 213 mPageBoundSync = PagedView.INVALID_PAGE; 214 mDeferredExecutor = null; 215 mAppInfos = null; 216 } 217 verifySynchronouslyBound(int totalItems)218 public void verifySynchronouslyBound(int totalItems) { 219 // Verify that the requested page is bound synchronously 220 assertEquals(mPageBoundSync, mPageToBindSync); 221 assertEquals(mItems.size(), 1); 222 assertEquals(mItems.get(0).screenId, mPageBoundSync); 223 assertNotNull(mDeferredExecutor); 224 225 // Verify that all other pages are bound properly 226 mDeferredExecutor.runAllTasks(); 227 assertEquals(mItems.size(), totalItems); 228 } 229 verifyApps(String... apps)230 public void verifyApps(String... apps) { 231 assertEquals(apps.length, mAppInfos.length); 232 assertEquals(Arrays.stream(mAppInfos) 233 .map(ai -> ai.getTargetComponent().getPackageName()) 234 .collect(Collectors.toSet()), 235 new HashSet<>(Arrays.asList(apps))); 236 } 237 } 238 } 239