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