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