1package com.android.launcher3.model;
2
3import static org.mockito.Matchers.anyBoolean;
4import static org.mockito.Mockito.atLeast;
5import static org.mockito.Mockito.mock;
6import static org.mockito.Mockito.verify;
7import static org.mockito.Mockito.when;
8
9import android.content.ComponentName;
10import android.content.ContentResolver;
11import android.content.Context;
12import android.content.ContextWrapper;
13import android.content.Intent;
14import android.content.pm.LauncherActivityInfo;
15import android.content.res.Resources;
16import android.graphics.Bitmap;
17import android.graphics.Bitmap.Config;
18import android.graphics.Color;
19import android.os.Process;
20import android.os.UserHandle;
21import android.support.annotation.NonNull;
22import android.support.test.InstrumentationRegistry;
23import android.support.test.rule.provider.ProviderTestRule;
24
25import com.android.launcher3.AllAppsList;
26import com.android.launcher3.AppFilter;
27import com.android.launcher3.AppInfo;
28import com.android.launcher3.IconCache;
29import com.android.launcher3.InvariantDeviceProfile;
30import com.android.launcher3.ItemInfo;
31import com.android.launcher3.LauncherAppState;
32import com.android.launcher3.LauncherModel;
33import com.android.launcher3.LauncherModel.Callbacks;
34import com.android.launcher3.LauncherModel.ModelUpdateTask;
35import com.android.launcher3.LauncherProvider;
36import com.android.launcher3.graphics.BitmapInfo;
37import com.android.launcher3.util.ComponentKey;
38import com.android.launcher3.util.Provider;
39import com.android.launcher3.util.TestLauncherProvider;
40
41import org.junit.Before;
42import org.junit.Rule;
43import org.mockito.ArgumentCaptor;
44
45import java.io.BufferedReader;
46import java.io.InputStreamReader;
47import java.lang.reflect.Field;
48import java.util.HashMap;
49import java.util.List;
50import java.util.concurrent.Executor;
51
52/**
53 * Base class for writing tests for Model update tasks.
54 */
55public 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