2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.dashboard;
19 import static com.android.settings.dashboard.DashboardData.STABLE_ID_CONDITION_CONTAINER;
20 import static com.android.settings.dashboard.DashboardData.STABLE_ID_CONDITION_FOOTER;
21 import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONTAINER;
22 import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONDITION_DIVIDER;
23 import static com.google.common.truth.Truth.assertThat;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.when;
27 import android.app.PendingIntent;
28 import android.service.settings.suggestions.Suggestion;
29 import android.support.annotation.NonNull;
30 import android.support.v7.util.DiffUtil;
31 import android.support.v7.util.ListUpdateCallback;
33 import com.android.settings.TestConfig;
34 import com.android.settings.dashboard.conditional.AirplaneModeCondition;
35 import com.android.settings.dashboard.conditional.Condition;
36 import com.android.settingslib.drawer.DashboardCategory;
37 import com.android.settingslib.drawer.Tile;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.mockito.Mock;
43 import org.mockito.MockitoAnnotations;
44 import org.robolectric.RobolectricTestRunner;
45 import org.robolectric.annotation.Config;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Objects;
53 @RunWith(RobolectricTestRunner.class)
54 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
55 public class DashboardDataTest {
56 private static final String TEST_SUGGESTION_TITLE = "Use fingerprint";
57 private static final String TEST_CATEGORY_TILE_TITLE = "Display";
59 private DashboardData mDashboardDataWithOneConditions;
60 private DashboardData mDashboardDataWithTwoConditions;
61 private DashboardData mDashboardDataWithNoItems;
62 private DashboardCategory mDashboardCategory;
64 private Tile mTestCategoryTile;
66 private Condition mTestCondition;
68 private Condition mSecondCondition; // condition used to test insert in DiffUtil
69 private Suggestion mTestSuggestion;
73 MockitoAnnotations.initMocks(this);
75 mDashboardCategory = new DashboardCategory();
78 final List<Suggestion> suggestions = new ArrayList<>();
79 mTestSuggestion = new Suggestion.Builder("pkg")
80 .setTitle(TEST_SUGGESTION_TITLE)
81 .setPendingIntent(mock(PendingIntent.class))
83 suggestions.add(mTestSuggestion);
85 // Build oneItemConditions
86 final List<Condition> oneItemConditions = new ArrayList<>();
87 when(mTestCondition.shouldShow()).thenReturn(true);
88 oneItemConditions.add(mTestCondition);
90 // Build twoItemConditions
91 final List<Condition> twoItemsConditions = new ArrayList<>();
92 when(mSecondCondition.shouldShow()).thenReturn(true);
93 twoItemsConditions.add(mTestCondition);
94 twoItemsConditions.add(mSecondCondition);
97 mTestCategoryTile.title = TEST_CATEGORY_TILE_TITLE;
98 mDashboardCategory.title = "test";
100 mDashboardCategory.addTile(mTestCategoryTile);
102 // Build DashboardData
103 mDashboardDataWithOneConditions = new DashboardData.Builder()
104 .setConditions(oneItemConditions)
105 .setCategory(mDashboardCategory)
106 .setSuggestions(suggestions)
107 .setConditionExpanded(true)
110 mDashboardDataWithTwoConditions = new DashboardData.Builder()
111 .setConditions(twoItemsConditions)
112 .setCategory(mDashboardCategory)
113 .setSuggestions(suggestions)
114 .setConditionExpanded(true)
117 mDashboardDataWithNoItems = new DashboardData.Builder()
120 .setSuggestions(null)
125 public void testBuildItemsData_shouldSetstableId() {
126 final List<DashboardData.Item> items = mDashboardDataWithOneConditions.getItemList();
128 // suggestion, seperator, condition, footer, 1 tile
129 assertThat(items).hasSize(5);
131 assertThat(items.get(0).id).isEqualTo(STABLE_ID_SUGGESTION_CONTAINER);
132 assertThat(items.get(1).id).isEqualTo(STABLE_ID_SUGGESTION_CONDITION_DIVIDER);
133 assertThat(items.get(2).id).isEqualTo(STABLE_ID_CONDITION_CONTAINER);
134 assertThat(items.get(3).id).isEqualTo(STABLE_ID_CONDITION_FOOTER);
135 assertThat(items.get(4).id).isEqualTo(Objects.hash(mTestCategoryTile.title));
139 public void testBuildItemsData_containsAllData() {
140 final Object[] expectedObjects = {
141 mDashboardDataWithOneConditions.getSuggestions(),
143 mDashboardDataWithOneConditions.getConditions(),
144 null /* footer */, mTestCategoryTile};
145 final int expectedSize = expectedObjects.length;
147 assertThat(mDashboardDataWithOneConditions.getItemList()).hasSize(expectedSize);
149 for (int i = 0; i < expectedSize; i++) {
150 final Object item = mDashboardDataWithOneConditions.getItemEntityByPosition(i);
151 if (item instanceof List) {
152 assertThat(item).isEqualTo(expectedObjects[i]);
153 } else if (item instanceof DashboardData.ConditionHeaderData) {
154 DashboardData.ConditionHeaderData i1 =
155 (DashboardData.ConditionHeaderData) item;
156 DashboardData.ConditionHeaderData i2 =
157 (DashboardData.ConditionHeaderData) expectedObjects[i];
158 assertThat(i1.title).isEqualTo(i2.title);
159 assertThat(i1.conditionCount).isEqualTo(i2.conditionCount);
161 assertThat(item).isSameAs(expectedObjects[i]);
167 public void testGetPositionByEntity_selfInstance_returnPositionFound() {
168 final int position = mDashboardDataWithOneConditions
169 .getPositionByEntity(mDashboardDataWithOneConditions.getConditions());
170 assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
174 public void testGetPositionByEntity_notExisted_returnNotFound() {
175 final Condition condition = mock(AirplaneModeCondition.class);
176 final int position = mDashboardDataWithOneConditions.getPositionByEntity(condition);
177 assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
181 public void testGetPositionByTile_selfInstance_returnPositionFound() {
182 final int position = mDashboardDataWithOneConditions
183 .getPositionByTile(mTestCategoryTile);
184 assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
188 public void testGetPositionByTile_equalTitle_returnPositionFound() {
189 final Tile tile = mock(Tile.class);
190 tile.title = TEST_CATEGORY_TILE_TITLE;
191 final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
192 assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
196 public void testGetPositionByTile_notExisted_returnNotFound() {
197 final Tile tile = mock(Tile.class);
199 final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
200 assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
204 public void testDiffUtil_DataEqual_noResultData() {
205 List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
206 testDiffUtil(mDashboardDataWithOneConditions,
207 mDashboardDataWithOneConditions, testResultData);
211 public void testDiffUtil_InsertOneCondition_ResultDataOneChanged() {
212 //Build testResultData
213 final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
214 // Item in position 3 is the condition container containing the list of conditions, which
216 testResultData.add(new ListUpdateResult.ResultData(
217 ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 2, 1));
219 testDiffUtil(mDashboardDataWithOneConditions,
220 mDashboardDataWithTwoConditions, testResultData);
224 public void testDiffUtil_RemoveOneSuggestion_causeItemRemoveAndChange() {
225 //Build testResultData
226 final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
227 // removed suggestion and the divider
228 testResultData.add(new ListUpdateResult.ResultData(
229 ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 2));
230 testResultData.add(new ListUpdateResult.ResultData(
231 ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 2, 1));
232 // Build DashboardData
233 final List<Condition> oneItemConditions = new ArrayList<>();
234 when(mTestCondition.shouldShow()).thenReturn(true);
235 oneItemConditions.add(mTestCondition);
236 final List<Suggestion> suggestions = new ArrayList<>();
237 suggestions.add(mTestSuggestion);
239 final DashboardData oldData = new DashboardData.Builder()
240 .setConditions(oneItemConditions)
241 .setCategory(mDashboardCategory)
242 .setSuggestions(suggestions)
243 .setConditionExpanded(false)
245 final DashboardData newData = new DashboardData.Builder()
246 .setConditions(oneItemConditions)
247 .setSuggestions(null)
248 .setCategory(mDashboardCategory)
249 .setConditionExpanded(false)
252 testDiffUtil(oldData, newData, testResultData);
256 public void testDiffUtil_DeleteAllData_ResultDataOneDeleted() {
257 //Build testResultData
258 final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
259 testResultData.add(new ListUpdateResult.ResultData(
260 ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 5));
262 testDiffUtil(mDashboardDataWithOneConditions, mDashboardDataWithNoItems, testResultData);
266 public void testDiffUtil_typeSuggestedContainer_ResultDataNothingChanged() {
267 //Build testResultData
268 final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
270 DashboardData prevData = new DashboardData.Builder()
273 .setSuggestions(Arrays.asList(mTestSuggestion))
275 DashboardData currentData = new DashboardData.Builder()
278 .setSuggestions(Arrays.asList(mTestSuggestion))
280 testDiffUtil(prevData, currentData, testResultData);
284 * Test when using the
285 * {@link com.android.settings.dashboard.DashboardData.ItemsDataDiffCallback}
286 * to transfer List from {@paramref baseDashboardData} to {@paramref diffDashboardData}, whether
287 * the transform data result is equals to {@paramref testResultData}
289 * The steps are described below:
290 * 1. Calculate a {@link android.support.v7.util.DiffUtil.DiffResult} from
291 * {@paramref baseDashboardData} to {@paramref diffDashboardData}
293 * 2. Dispatch the {@link android.support.v7.util.DiffUtil.DiffResult} calculated from step 1
294 * into {@link ListUpdateResult}
296 * 3. Get result data(a.k.a. baseResultData) from {@link ListUpdateResult} and compare it to
297 * {@paramref testResultData}
299 * Because baseResultData and {@paramref testResultData} don't have sequence. When do the
300 * comparison, we will sort them first and then compare the inside data from them one by one.
302 private void testDiffUtil(DashboardData baseDashboardData, DashboardData diffDashboardData,
303 List<ListUpdateResult.ResultData> testResultData) {
304 final DiffUtil.DiffResult diffUtilResult = DiffUtil.calculateDiff(
305 new DashboardData.ItemsDataDiffCallback(
306 baseDashboardData.getItemList(), diffDashboardData.getItemList()));
308 // Dispatch to listUpdateResult, then listUpdateResult will have result data
309 final ListUpdateResult listUpdateResult = new ListUpdateResult();
310 diffUtilResult.dispatchUpdatesTo(listUpdateResult);
312 final List<ListUpdateResult.ResultData> baseResultData = listUpdateResult.getResultData();
313 assertThat(testResultData.size()).isEqualTo(baseResultData.size());
315 // Sort them so we can compare them one by one using a for loop
316 Collections.sort(baseResultData);
317 Collections.sort(testResultData);
318 final int size = baseResultData.size();
319 for (int i = 0; i < size; i++) {
320 // Refer to equals method in ResultData
321 assertThat(baseResultData.get(i)).isEqualTo(testResultData.get(i));
326 * This class contains the result about how the changes made to convert one
327 * list to another list. It implements ListUpdateCallback to record the result data.
329 private static class ListUpdateResult implements ListUpdateCallback {
330 final private List<ResultData> mResultData;
332 public ListUpdateResult() {
333 mResultData = new ArrayList<>();
336 public List<ResultData> getResultData() {
341 public void onInserted(int position, int count) {
342 mResultData.add(new ResultData(ResultData.TYPE_OPERATION_INSERT, position, count));
346 public void onRemoved(int position, int count) {
347 mResultData.add(new ResultData(ResultData.TYPE_OPERATION_REMOVE, position, count));
351 public void onMoved(int fromPosition, int toPosition) {
353 new ResultData(ResultData.TYPE_OPERATION_MOVE, fromPosition, toPosition));
357 public void onChanged(int position, int count, Object payload) {
358 mResultData.add(new ResultData(ResultData.TYPE_OPERATION_CHANGE, position, count));
362 * This class contains general type and field to record the operation data generated
363 * in {@link ListUpdateCallback}. Please refer to {@link ListUpdateCallback} for more info.
365 * The following are examples about the data stored in this class:
367 * "The data starts from position(arg1) with count number(arg2) is changed(operation)"
368 * or "The data is moved(operation) from position1(arg1) to position2(arg2)"
370 private static class ResultData implements Comparable<ResultData> {
371 public static final int TYPE_OPERATION_INSERT = 0;
372 public static final int TYPE_OPERATION_REMOVE = 1;
373 public static final int TYPE_OPERATION_MOVE = 2;
374 public static final int TYPE_OPERATION_CHANGE = 3;
376 public final int operation;
377 public final int arg1;
378 public final int arg2;
380 public ResultData(int operation, int arg1, int arg2) {
381 this.operation = operation;
387 public boolean equals(Object obj) {
392 if (!(obj instanceof ResultData)) {
396 ResultData targetData = (ResultData) obj;
398 return operation == targetData.operation && arg1 == targetData.arg1
399 && arg2 == targetData.arg2;
403 public int compareTo(@NonNull ResultData resultData) {
404 if (this.operation != resultData.operation) {
405 return operation - resultData.operation;
408 if (arg1 != resultData.arg1) {
409 return arg1 - resultData.arg1;
412 return arg2 - resultData.arg2;
416 public String toString() {
417 return "op:" + operation + ",arg1:" + arg1 + ",arg2:" + arg2;