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