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