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