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.LauncherModelHelper.TEST_PACKAGE; 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertNotNull; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assert.assertTrue; 25 import static org.mockito.Mockito.spy; 26 27 import android.os.Process; 28 29 import androidx.test.ext.junit.runners.AndroidJUnit4; 30 import androidx.test.filters.SmallTest; 31 32 import com.android.launcher3.model.BgDataModel.Callbacks; 33 import com.android.launcher3.model.data.AppInfo; 34 import com.android.launcher3.model.data.ItemInfo; 35 import com.android.launcher3.util.Executors; 36 import com.android.launcher3.util.IntArray; 37 import com.android.launcher3.util.IntSet; 38 import com.android.launcher3.util.LauncherLayoutBuilder; 39 import com.android.launcher3.util.LauncherModelHelper; 40 import com.android.launcher3.util.PackageUserKey; 41 import com.android.launcher3.util.RunnableList; 42 import com.android.launcher3.util.TestUtil; 43 44 import org.junit.After; 45 import org.junit.Before; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.stream.Collectors; 55 56 /** 57 * Tests to verify multiple callbacks in Loader 58 */ 59 @SmallTest 60 @RunWith(AndroidJUnit4.class) 61 public class ModelMultiCallbacksTest { 62 63 private LauncherModelHelper mModelHelper; 64 65 @Before setUp()66 public void setUp() { 67 mModelHelper = new LauncherModelHelper(); 68 } 69 70 @After tearDown()71 public void tearDown() throws Exception { 72 mModelHelper.destroy(); 73 TestUtil.uninstallDummyApp(); 74 } 75 getCallbacks()76 private ModelLauncherCallbacks getCallbacks() { 77 return mModelHelper.getModel().newModelCallbacks(); 78 } 79 80 @Test testTwoCallbacks_loadedTogether()81 public void testTwoCallbacks_loadedTogether() throws Exception { 82 setupWorkspacePages(3); 83 84 MyCallbacks cb1 = spy(MyCallbacks.class); 85 Executors.MAIN_EXECUTOR.execute(() -> 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 = IntSet.wrap(2); 94 Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2)); 95 96 waitForLoaderAndTempMainThread(); 97 assertFalse(cb1.bindStarted); 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 Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2)); 106 waitForLoaderAndTempMainThread(); 107 assertNull(cb1.mPendingTasks); 108 assertNull(cb2.mPendingTasks); 109 110 // Reloading only loads registered callbacks 111 mModelHelper.getModel().startLoader(); 112 waitForLoaderAndTempMainThread(); 113 cb1.verifySynchronouslyBound(3); 114 assertNull(cb2.mPendingTasks); 115 } 116 117 @Test testTwoCallbacks_receiveUpdates()118 public void testTwoCallbacks_receiveUpdates() throws Exception { 119 TestUtil.uninstallDummyApp(); 120 121 setupWorkspacePages(1); 122 123 MyCallbacks cb1 = spy(MyCallbacks.class); 124 MyCallbacks cb2 = spy(MyCallbacks.class); 125 Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb1)); 126 Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().addCallbacksAndLoad(cb2)); 127 waitForLoaderAndTempMainThread(); 128 129 assertTrue(cb1.allApps().contains(TEST_PACKAGE)); 130 assertTrue(cb2.allApps().contains(TEST_PACKAGE)); 131 132 // Install package 1 133 TestUtil.installDummyApp(); 134 getCallbacks().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle()); 135 waitForLoaderAndTempMainThread(); 136 assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE)); 137 assertTrue(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE)); 138 139 // Uninstall package 2 140 TestUtil.uninstallDummyApp(); 141 getCallbacks().onPackageRemoved(TestUtil.DUMMY_PACKAGE, Process.myUserHandle()); 142 waitForLoaderAndTempMainThread(); 143 assertFalse(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE)); 144 assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE)); 145 146 // Unregister a callback and verify updates no longer received 147 Executors.MAIN_EXECUTOR.execute(() -> mModelHelper.getModel().removeCallbacks(cb2)); 148 TestUtil.installDummyApp(); 149 getCallbacks().onPackageAdded(TestUtil.DUMMY_PACKAGE, Process.myUserHandle()); 150 waitForLoaderAndTempMainThread(); 151 152 // cb2 didn't get the update 153 assertTrue(cb1.allApps().contains(TestUtil.DUMMY_PACKAGE)); 154 assertFalse(cb2.allApps().contains(TestUtil.DUMMY_PACKAGE)); 155 } 156 waitForLoaderAndTempMainThread()157 private void waitForLoaderAndTempMainThread() throws Exception { 158 Executors.MAIN_EXECUTOR.submit(() -> { }).get(); 159 Executors.MODEL_EXECUTOR.submit(() -> { }).get(); 160 Executors.MAIN_EXECUTOR.submit(() -> { }).get(); 161 } 162 setupWorkspacePages(int pageCount)163 private void setupWorkspacePages(int pageCount) throws Exception { 164 // Create a layout with 3 pages 165 LauncherLayoutBuilder builder = new LauncherLayoutBuilder(); 166 for (int i = 0; i < pageCount; i++) { 167 builder.atWorkspace(1, 1, i).putApp(TEST_PACKAGE, TEST_PACKAGE); 168 } 169 mModelHelper.setupDefaultLayoutProvider(builder); 170 } 171 172 private abstract static class MyCallbacks implements Callbacks { 173 174 final List<ItemInfo> mItems = new ArrayList<>(); 175 IntSet mPageToBindSync = IntSet.wrap(0); 176 IntSet mPageBoundSync = new IntSet(); 177 RunnableList mPendingTasks; 178 AppInfo[] mAppInfos; 179 boolean bindStarted; 180 MyCallbacks()181 MyCallbacks() { } 182 183 @Override startBinding()184 public void startBinding() { 185 bindStarted = true; 186 } 187 188 @Override onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync)189 public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, 190 RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { 191 mPageBoundSync = boundPages; 192 mPendingTasks = pendingTasks; 193 } 194 195 @Override bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)196 public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { 197 mItems.addAll(shortcuts); 198 } 199 200 @Override bindAllApplications(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> packageUserKeytoUidMap)201 public void bindAllApplications(AppInfo[] apps, int flags, 202 Map<PackageUserKey, Integer> packageUserKeytoUidMap) { 203 mAppInfos = apps; 204 } 205 206 @Override getPagesToBindSynchronously(IntArray orderedScreenIds)207 public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) { 208 return mPageToBindSync; 209 } 210 reset()211 public void reset() { 212 mItems.clear(); 213 mPageBoundSync = new IntSet(); 214 mPendingTasks = null; 215 mAppInfos = null; 216 bindStarted = false; 217 } 218 verifySynchronouslyBound(int totalItems)219 public void verifySynchronouslyBound(int totalItems) { 220 // Verify that the requested page is bound synchronously 221 assertTrue(bindStarted); 222 assertEquals(mPageToBindSync, mPageBoundSync); 223 assertEquals(mItems.size(), 1); 224 assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync); 225 assertNotNull(mPendingTasks); 226 227 // Verify that all other pages are bound properly 228 mPendingTasks.executeAllAndDestroy(); 229 assertEquals(mItems.size(), totalItems); 230 } 231 allApps()232 public Set<String> allApps() { 233 return Arrays.stream(mAppInfos) 234 .map(ai -> ai.getTargetComponent().getPackageName()) 235 .collect(Collectors.toSet()); 236 } 237 verifyApps(String... apps)238 public void verifyApps(String... apps) { 239 assertTrue(allApps().containsAll(Arrays.asList(apps))); 240 } 241 } 242 } 243