1 package com.android.launcher3.model;
2 
3 import android.content.ComponentName;
4 import android.content.Context;
5 import android.content.Intent;
6 import android.content.pm.LauncherActivityInfo;
7 import android.content.res.Resources;
8 import android.graphics.Bitmap;
9 import android.graphics.Bitmap.Config;
10 import android.os.Process;
11 import android.os.UserHandle;
12 import android.support.annotation.NonNull;
13 import android.support.test.InstrumentationRegistry;
14 import android.test.ProviderTestCase2;
15 
16 import com.android.launcher3.AllAppsList;
17 import com.android.launcher3.AppFilter;
18 import com.android.launcher3.AppInfo;
19 import com.android.launcher3.DeferredHandler;
20 import com.android.launcher3.IconCache;
21 import com.android.launcher3.InvariantDeviceProfile;
22 import com.android.launcher3.ItemInfo;
23 import com.android.launcher3.LauncherAppState;
24 import com.android.launcher3.LauncherModel;
25 import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
26 import com.android.launcher3.LauncherModel.Callbacks;
27 import com.android.launcher3.config.ProviderConfig;
28 import com.android.launcher3.util.ComponentKey;
29 import com.android.launcher3.util.Provider;
30 import com.android.launcher3.util.TestLauncherProvider;
31 
32 import org.mockito.ArgumentCaptor;
33 
34 import java.io.BufferedReader;
35 import java.io.InputStreamReader;
36 import java.lang.reflect.Field;
37 import java.util.HashMap;
38 import java.util.List;
39 
40 import static org.mockito.Matchers.anyBoolean;
41 import static org.mockito.Mockito.atLeast;
42 import static org.mockito.Mockito.mock;
43 import static org.mockito.Mockito.verify;
44 import static org.mockito.Mockito.when;
45 
46 /**
47  * Base class for writing tests for Model update tasks.
48  */
49 public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
50 
51     public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
52 
53     public Context targetContext;
54     public UserHandle myUser;
55 
56     public InvariantDeviceProfile idp;
57     public LauncherAppState appState;
58     public LauncherModel model;
59     public ModelWriter modelWriter;
60     public MyIconCache iconCache;
61 
62     public BgDataModel bgDataModel;
63     public AllAppsList allAppsList;
64     public Callbacks callbacks;
65 
BaseModelUpdateTaskTestCase()66     public BaseModelUpdateTaskTestCase() {
67         super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
68     }
69 
70     @Override
setUp()71     protected void setUp() throws Exception {
72         super.setUp();
73 
74         callbacks = mock(Callbacks.class);
75         appState = mock(LauncherAppState.class);
76         model = mock(LauncherModel.class);
77         modelWriter = mock(ModelWriter.class);
78         when(appState.getModel()).thenReturn(model);
79         when(model.getWriter(anyBoolean())).thenReturn(modelWriter);
80 
81         myUser = Process.myUserHandle();
82 
83         bgDataModel = new BgDataModel();
84         targetContext = InstrumentationRegistry.getTargetContext();
85         idp = new InvariantDeviceProfile();
86         iconCache = new MyIconCache(targetContext, idp);
87 
88         allAppsList = new AllAppsList(iconCache, new AppFilter());
89 
90         when(appState.getIconCache()).thenReturn(iconCache);
91         when(appState.getInvariantDeviceProfile()).thenReturn(idp);
92     }
93 
94     /**
95      * Synchronously executes the task and returns all the UI callbacks posted.
96      */
executeTaskForTest(BaseModelUpdateTask task)97     public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
98         LauncherModel mockModel = mock(LauncherModel.class);
99         when(mockModel.getCallback()).thenReturn(callbacks);
100 
101         Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
102         f.setAccessible(true);
103         f.set(task, mockModel);
104 
105         DeferredHandler mockHandler = mock(DeferredHandler.class);
106         f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
107         f.setAccessible(true);
108         f.set(task, mockHandler);
109 
110         task.execute(appState, bgDataModel, allAppsList);
111         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
112         verify(mockHandler, atLeast(0)).post(captor.capture());
113 
114         return captor.getAllValues();
115     }
116 
117     /**
118      * Initializes mock data for the test.
119      */
initializeData(String resourceName)120     public void initializeData(String resourceName) throws Exception {
121         Context myContext = InstrumentationRegistry.getContext();
122         Resources res = myContext.getResources();
123         int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
124         try (BufferedReader reader =
125                      new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
126             String line;
127             HashMap<String, Class> classMap = new HashMap<>();
128             while((line = reader.readLine()) != null) {
129                 line = line.trim();
130                 if (line.startsWith("#") || line.isEmpty()) {
131                     continue;
132                 }
133                 String[] commands = line.split(" ");
134                 switch (commands[0]) {
135                     case "classMap":
136                         classMap.put(commands[1], Class.forName(commands[2]));
137                         break;
138                     case "bgItem":
139                         bgDataModel.addItem(targetContext,
140                                 (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
141                         break;
142                     case "allApps":
143                         allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
144                         break;
145                 }
146             }
147         }
148     }
149 
initItem(Class clazz, String[] fieldDef, int startIndex)150     private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
151         HashMap<String, Field> cache = fieldCache.get(clazz);
152         if (cache == null) {
153             cache = new HashMap<>();
154             Class c = clazz;
155             while (c != null) {
156                 for (Field f : c.getDeclaredFields()) {
157                     f.setAccessible(true);
158                     cache.put(f.getName(), f);
159                 }
160                 c = c.getSuperclass();
161             }
162             fieldCache.put(clazz, cache);
163         }
164 
165         Object item = clazz.newInstance();
166         for (int i = startIndex; i < fieldDef.length; i++) {
167             String[] fieldData = fieldDef[i].split("=", 2);
168             Field f = cache.get(fieldData[0]);
169             Class type = f.getType();
170             if (type == int.class || type == long.class) {
171                 f.set(item, Integer.parseInt(fieldData[1]));
172             } else if (type == CharSequence.class || type == String.class) {
173                 f.set(item, fieldData[1]);
174             } else if (type == Intent.class) {
175                 if (!fieldData[1].startsWith("#Intent")) {
176                     fieldData[1] = "#Intent;" + fieldData[1] + ";end";
177                 }
178                 f.set(item, Intent.parseUri(fieldData[1], 0));
179             } else if (type == ComponentName.class) {
180                 f.set(item, ComponentName.unflattenFromString(fieldData[1]));
181             } else {
182                 throw new Exception("Added parsing logic for "
183                         + f.getName() + " of type " + f.getType());
184             }
185         }
186         return item;
187     }
188 
189     public static class MyIconCache extends IconCache {
190 
191         private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
192 
MyIconCache(Context context, InvariantDeviceProfile idp)193         public MyIconCache(Context context, InvariantDeviceProfile idp) {
194             super(context, idp);
195         }
196 
197         @Override
cacheLocked( @onNull ComponentName componentName, @NonNull Provider<LauncherActivityInfo> infoProvider, UserHandle user, boolean usePackageIcon, boolean useLowResIcon)198         protected CacheEntry cacheLocked(
199                 @NonNull ComponentName componentName,
200                 @NonNull Provider<LauncherActivityInfo> infoProvider,
201                 UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
202             CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
203             if (entry == null) {
204                 entry = new CacheEntry();
205                 entry.icon = getDefaultIcon(user);
206             }
207             return entry;
208         }
209 
addCache(ComponentName key, String title)210         public void addCache(ComponentName key, String title) {
211             CacheEntry entry = new CacheEntry();
212             entry.icon = newIcon();
213             entry.title = title;
214             mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
215         }
216 
newIcon()217         public Bitmap newIcon() {
218             return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
219         }
220 
221         @Override
makeDefaultIcon(UserHandle user)222         protected Bitmap makeDefaultIcon(UserHandle user) {
223             return newIcon();
224         }
225     }
226 }
227