• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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