• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.settings.dashboard;
18 
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_CONDITION_DIVIDER;
22 import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONTAINER;
23 import static com.google.common.truth.Truth.assertThat;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.when;
26 
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;
32 
33 import com.android.settings.dashboard.conditional.AirplaneModeCondition;
34 import com.android.settings.dashboard.conditional.Condition;
35 import com.android.settingslib.drawer.DashboardCategory;
36 import com.android.settingslib.drawer.Tile;
37 
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.mockito.Mock;
42 import org.mockito.MockitoAnnotations;
43 import org.robolectric.RobolectricTestRunner;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Objects;
49 
50 @RunWith(RobolectricTestRunner.class)
51 public class DashboardDataTest {
52 
53     private static final String TEST_SUGGESTION_TITLE = "Use fingerprint";
54     private static final String TEST_CATEGORY_TILE_TITLE = "Display";
55 
56     private DashboardData mDashboardDataWithOneConditions;
57     private DashboardData mDashboardDataWithTwoConditions;
58     private DashboardData mDashboardDataWithNoItems;
59     private DashboardCategory mDashboardCategory;
60     @Mock
61     private Tile mTestCategoryTile;
62     @Mock
63     private Condition mTestCondition;
64     @Mock
65     private Condition mSecondCondition; // condition used to test insert in DiffUtil
66     private Suggestion mTestSuggestion;
67 
68     @Before
SetUp()69     public void SetUp() {
70         MockitoAnnotations.initMocks(this);
71 
72         mDashboardCategory = new DashboardCategory();
73 
74         // Build suggestions
75         final List<Suggestion> suggestions = new ArrayList<>();
76         mTestSuggestion = new Suggestion.Builder("pkg")
77                 .setTitle(TEST_SUGGESTION_TITLE)
78                 .setPendingIntent(mock(PendingIntent.class))
79                 .build();
80         suggestions.add(mTestSuggestion);
81 
82         // Build oneItemConditions
83         final List<Condition> oneItemConditions = new ArrayList<>();
84         when(mTestCondition.shouldShow()).thenReturn(true);
85         oneItemConditions.add(mTestCondition);
86 
87         // Build twoItemConditions
88         final List<Condition> twoItemsConditions = new ArrayList<>();
89         when(mSecondCondition.shouldShow()).thenReturn(true);
90         twoItemsConditions.add(mTestCondition);
91         twoItemsConditions.add(mSecondCondition);
92 
93         // Build category
94         mTestCategoryTile.title = TEST_CATEGORY_TILE_TITLE;
95         mDashboardCategory.title = "test";
96 
97         mDashboardCategory.addTile(mTestCategoryTile);
98 
99         // Build DashboardData
100         mDashboardDataWithOneConditions = new DashboardData.Builder()
101                 .setConditions(oneItemConditions)
102                 .setCategory(mDashboardCategory)
103                 .setSuggestions(suggestions)
104                 .setConditionExpanded(true)
105                 .build();
106 
107         mDashboardDataWithTwoConditions = new DashboardData.Builder()
108                 .setConditions(twoItemsConditions)
109                 .setCategory(mDashboardCategory)
110                 .setSuggestions(suggestions)
111                 .setConditionExpanded(true)
112                 .build();
113 
114         mDashboardDataWithNoItems = new DashboardData.Builder()
115                 .setConditions(null)
116                 .setCategory(null)
117                 .setSuggestions(null)
118                 .build();
119     }
120 
121     @Test
testBuildItemsData_shouldSetstableId()122     public void testBuildItemsData_shouldSetstableId() {
123         final List<DashboardData.Item> items = mDashboardDataWithOneConditions.getItemList();
124 
125         // suggestion, separator, condition, footer, 1 tile
126         assertThat(items).hasSize(5);
127 
128         assertThat(items.get(0).id).isEqualTo(STABLE_ID_SUGGESTION_CONTAINER);
129         assertThat(items.get(1).id).isEqualTo(STABLE_ID_SUGGESTION_CONDITION_DIVIDER);
130         assertThat(items.get(2).id).isEqualTo(STABLE_ID_CONDITION_CONTAINER);
131         assertThat(items.get(3).id).isEqualTo(STABLE_ID_CONDITION_FOOTER);
132         assertThat(items.get(4).id).isEqualTo(Objects.hash(mTestCategoryTile.title));
133     }
134 
135     @Test
testBuildItemsData_containsAllData()136     public void testBuildItemsData_containsAllData() {
137         final Object[] expectedObjects = {
138                 mDashboardDataWithOneConditions.getSuggestions(),
139                 null /* divider */,
140                 mDashboardDataWithOneConditions.getConditions(),
141                 null /* footer */, mTestCategoryTile};
142         final int expectedSize = expectedObjects.length;
143 
144         assertThat(mDashboardDataWithOneConditions.getItemList()).hasSize(expectedSize);
145 
146         for (int i = 0; i < expectedSize; i++) {
147             final Object item = mDashboardDataWithOneConditions.getItemEntityByPosition(i);
148             if (item instanceof List) {
149                 assertThat(item).isEqualTo(expectedObjects[i]);
150             } else if (item instanceof DashboardData.ConditionHeaderData) {
151                 DashboardData.ConditionHeaderData i1 = (DashboardData.ConditionHeaderData) item;
152                 DashboardData.ConditionHeaderData i2 =
153                         (DashboardData.ConditionHeaderData) expectedObjects[i];
154                 assertThat(i1.title).isEqualTo(i2.title);
155                 assertThat(i1.conditionCount).isEqualTo(i2.conditionCount);
156             } else {
157                 assertThat(item).isSameAs(expectedObjects[i]);
158             }
159         }
160     }
161 
162     @Test
testGetPositionByEntity_selfInstance_returnPositionFound()163     public void testGetPositionByEntity_selfInstance_returnPositionFound() {
164         final int position = mDashboardDataWithOneConditions
165                 .getPositionByEntity(mDashboardDataWithOneConditions.getConditions());
166         assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
167     }
168 
169     @Test
testGetPositionByEntity_notExisted_returnNotFound()170     public void testGetPositionByEntity_notExisted_returnNotFound() {
171         final Condition condition = mock(AirplaneModeCondition.class);
172         final int position = mDashboardDataWithOneConditions.getPositionByEntity(condition);
173         assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
174     }
175 
176     @Test
testGetPositionByTile_selfInstance_returnPositionFound()177     public void testGetPositionByTile_selfInstance_returnPositionFound() {
178         final int position = mDashboardDataWithOneConditions.getPositionByTile(mTestCategoryTile);
179         assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
180     }
181 
182     @Test
testGetPositionByTile_equalTitle_returnPositionFound()183     public void testGetPositionByTile_equalTitle_returnPositionFound() {
184         final Tile tile = mock(Tile.class);
185         tile.title = TEST_CATEGORY_TILE_TITLE;
186         final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
187         assertThat(position).isNotEqualTo(DashboardData.POSITION_NOT_FOUND);
188     }
189 
190     @Test
testGetPositionByTile_notExisted_returnNotFound()191     public void testGetPositionByTile_notExisted_returnNotFound() {
192         final Tile tile = mock(Tile.class);
193         tile.title = "";
194         final int position = mDashboardDataWithOneConditions.getPositionByTile(tile);
195         assertThat(position).isEqualTo(DashboardData.POSITION_NOT_FOUND);
196     }
197 
198     @Test
testDiffUtil_DataEqual_noResultData()199     public void testDiffUtil_DataEqual_noResultData() {
200         List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
201         testDiffUtil(mDashboardDataWithOneConditions,
202                 mDashboardDataWithOneConditions, testResultData);
203     }
204 
205     @Test
testDiffUtil_InsertOneCondition_ResultDataOneChanged()206     public void testDiffUtil_InsertOneCondition_ResultDataOneChanged() {
207         final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
208         // Item in position 3 is the condition container containing the list of conditions, which
209         // gets 1 more item
210         testResultData.add(new ListUpdateResult.ResultData(
211                 ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 2, 1));
212 
213         testDiffUtil(mDashboardDataWithOneConditions,
214                 mDashboardDataWithTwoConditions, testResultData);
215     }
216 
217     @Test
testDiffUtil_RemoveOneSuggestion_causeItemRemoveAndChange()218     public void testDiffUtil_RemoveOneSuggestion_causeItemRemoveAndChange() {
219         final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
220         // removed suggestion and the divider
221         testResultData.add(new ListUpdateResult.ResultData(
222                 ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 2));
223         testResultData.add(new ListUpdateResult.ResultData(
224                 ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 2, 1));
225         // Build DashboardData
226         final List<Condition> oneItemConditions = new ArrayList<>();
227         when(mTestCondition.shouldShow()).thenReturn(true);
228         oneItemConditions.add(mTestCondition);
229         final List<Suggestion> suggestions = new ArrayList<>();
230         suggestions.add(mTestSuggestion);
231 
232         final DashboardData oldData = new DashboardData.Builder()
233                 .setConditions(oneItemConditions)
234                 .setCategory(mDashboardCategory)
235                 .setSuggestions(suggestions)
236                 .setConditionExpanded(false)
237                 .build();
238         final DashboardData newData = new DashboardData.Builder()
239                 .setConditions(oneItemConditions)
240                 .setSuggestions(null)
241                 .setCategory(mDashboardCategory)
242                 .setConditionExpanded(false)
243                 .build();
244 
245         testDiffUtil(oldData, newData, testResultData);
246     }
247 
248     @Test
testDiffUtil_DeleteAllData_ResultDataOneDeleted()249     public void testDiffUtil_DeleteAllData_ResultDataOneDeleted() {
250         final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
251         testResultData.add(new ListUpdateResult.ResultData(
252                 ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 5));
253 
254         testDiffUtil(mDashboardDataWithOneConditions, mDashboardDataWithNoItems, testResultData);
255     }
256 
257     @Test
testDiffUtil_typeSuggestedContainer_ResultDataNothingChanged()258     public void testDiffUtil_typeSuggestedContainer_ResultDataNothingChanged() {
259         final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
260 
261         DashboardData prevData = new DashboardData.Builder()
262                 .setConditions(null)
263                 .setCategory(null)
264                 .setSuggestions(Collections.singletonList(mTestSuggestion))
265                 .build();
266         DashboardData currentData = new DashboardData.Builder()
267                 .setConditions(null)
268                 .setCategory(null)
269                 .setSuggestions(Collections.singletonList(mTestSuggestion))
270                 .build();
271         testDiffUtil(prevData, currentData, testResultData);
272     }
273 
274     /**
275      * Test when using the
276      * {@link com.android.settings.dashboard.DashboardData.ItemsDataDiffCallback}
277      * to transfer List from {@paramref baseDashboardData} to {@paramref diffDashboardData}, whether
278      * the transform data result is equals to {@paramref testResultData}
279      * <p>
280      * The steps are described below:
281      * 1. Calculate a {@link android.support.v7.util.DiffUtil.DiffResult} from
282      * {@paramref baseDashboardData} to {@paramref diffDashboardData}
283      * <p>
284      * 2. Dispatch the {@link android.support.v7.util.DiffUtil.DiffResult} calculated from step 1
285      * into {@link ListUpdateResult}
286      * <p>
287      * 3. Get result data(a.k.a. baseResultData) from {@link ListUpdateResult} and compare it to
288      * {@paramref testResultData}
289      * <p>
290      * Because baseResultData and {@paramref testResultData} don't have sequence. When do the
291      * comparison, we will sort them first and then compare the inside data from them one by one.
292      */
testDiffUtil(DashboardData baseDashboardData, DashboardData diffDashboardData, List<ListUpdateResult.ResultData> testResultData)293     private void testDiffUtil(DashboardData baseDashboardData, DashboardData diffDashboardData,
294             List<ListUpdateResult.ResultData> testResultData) {
295         final DiffUtil.DiffResult diffUtilResult = DiffUtil.calculateDiff(
296                 new DashboardData.ItemsDataDiffCallback(
297                         baseDashboardData.getItemList(), diffDashboardData.getItemList()));
298 
299         // Dispatch to listUpdateResult, then listUpdateResult will have result data
300         final ListUpdateResult listUpdateResult = new ListUpdateResult();
301         diffUtilResult.dispatchUpdatesTo(listUpdateResult);
302 
303         final List<ListUpdateResult.ResultData> baseResultData = listUpdateResult.getResultData();
304         assertThat(testResultData.size()).isEqualTo(baseResultData.size());
305 
306         // Sort them so we can compare them one by one using a for loop
307         Collections.sort(baseResultData);
308         Collections.sort(testResultData);
309         final int size = baseResultData.size();
310         for (int i = 0; i < size; i++) {
311             // Refer to equals method in ResultData
312             assertThat(baseResultData.get(i)).isEqualTo(testResultData.get(i));
313         }
314     }
315 
316     /**
317      * This class contains the result about how the changes made to convert one
318      * list to another list. It implements ListUpdateCallback to record the result data.
319      */
320     private static class ListUpdateResult implements ListUpdateCallback {
321         final private List<ResultData> mResultData;
322 
ListUpdateResult()323         public ListUpdateResult() {
324             mResultData = new ArrayList<>();
325         }
326 
getResultData()327         private List<ResultData> getResultData() {
328             return mResultData;
329         }
330 
331         @Override
onInserted(int position, int count)332         public void onInserted(int position, int count) {
333             mResultData.add(new ResultData(ResultData.TYPE_OPERATION_INSERT, position, count));
334         }
335 
336         @Override
onRemoved(int position, int count)337         public void onRemoved(int position, int count) {
338             mResultData.add(new ResultData(ResultData.TYPE_OPERATION_REMOVE, position, count));
339         }
340 
341         @Override
onMoved(int fromPosition, int toPosition)342         public void onMoved(int fromPosition, int toPosition) {
343             mResultData.add(
344                     new ResultData(ResultData.TYPE_OPERATION_MOVE, fromPosition, toPosition));
345         }
346 
347         @Override
onChanged(int position, int count, Object payload)348         public void onChanged(int position, int count, Object payload) {
349             mResultData.add(new ResultData(ResultData.TYPE_OPERATION_CHANGE, position, count));
350         }
351 
352         /**
353          * This class contains general type and field to record the operation data generated
354          * in {@link ListUpdateCallback}. Please refer to {@link ListUpdateCallback} for more info.
355          * <p>
356          * The following are examples about the data stored in this class:
357          * <p>
358          * "The data starts from position(arg1) with count number(arg2) is changed(operation)"
359          * or "The data is moved(operation) from position1(arg1) to position2(arg2)"
360          */
361         private static class ResultData implements Comparable<ResultData> {
362 
363             private static final int TYPE_OPERATION_INSERT = 0;
364             private static final int TYPE_OPERATION_REMOVE = 1;
365             private static final int TYPE_OPERATION_MOVE = 2;
366             private static final int TYPE_OPERATION_CHANGE = 3;
367 
368             private final int operation;
369             private final int arg1;
370             private final int arg2;
371 
ResultData(int operation, int arg1, int arg2)372             private ResultData(int operation, int arg1, int arg2) {
373                 this.operation = operation;
374                 this.arg1 = arg1;
375                 this.arg2 = arg2;
376             }
377 
378             @Override
equals(Object obj)379             public boolean equals(Object obj) {
380                 if (this == obj) {
381                     return true;
382                 }
383 
384                 if (!(obj instanceof ResultData)) {
385                     return false;
386                 }
387 
388                 ResultData targetData = (ResultData) obj;
389 
390                 return operation == targetData.operation && arg1 == targetData.arg1
391                         && arg2 == targetData.arg2;
392             }
393 
394             @Override
compareTo(@onNull ResultData resultData)395             public int compareTo(@NonNull ResultData resultData) {
396                 if (this.operation != resultData.operation) {
397                     return operation - resultData.operation;
398                 }
399 
400                 if (arg1 != resultData.arg1) {
401                     return arg1 - resultData.arg1;
402                 }
403 
404                 return arg2 - resultData.arg2;
405             }
406 
407             @Override
toString()408             public String toString() {
409                 return "op:" + operation + ",arg1:" + arg1 + ",arg2:" + arg2;
410             }
411         }
412     }
413 }
414