Change the way we generate stable id for DashboardSummary

- The countItem() method generates id based on position of Tile. This is
not truely stable.
- Added stable id constants for static views, and use hash of title as
stable id for tiles.

Bug: 33861822
Test: robotests
Change-Id: Ibbc88c82655503dc3964cb0c430c779dc9c93d41
This commit is contained in:
Fan Zhang
2017-06-29 12:58:39 -07:00
parent 6d1bb15f3b
commit a29346bab8
2 changed files with 89 additions and 70 deletions

View File

@@ -17,7 +17,7 @@ package com.android.settings.dashboard;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil; import android.support.v7.util.DiffUtil;
import android.text.TextUtils; import android.text.TextUtils;
@@ -30,6 +30,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Description about data list used in the DashboardAdapter. In the data list each item can be * Description about data list used in the DashboardAdapter. In the data list each item can be
@@ -42,25 +43,34 @@ public class DashboardData {
public static final int HEADER_MODE_SUGGESTION_EXPANDED = 1; public static final int HEADER_MODE_SUGGESTION_EXPANDED = 1;
public static final int HEADER_MODE_FULLY_EXPANDED = 2; public static final int HEADER_MODE_FULLY_EXPANDED = 2;
public static final int HEADER_MODE_COLLAPSED = 3; public static final int HEADER_MODE_COLLAPSED = 3;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({HEADER_MODE_DEFAULT, HEADER_MODE_SUGGESTION_EXPANDED, HEADER_MODE_FULLY_EXPANDED, @IntDef({HEADER_MODE_DEFAULT, HEADER_MODE_SUGGESTION_EXPANDED, HEADER_MODE_FULLY_EXPANDED,
HEADER_MODE_COLLAPSED}) HEADER_MODE_COLLAPSED})
public @interface HeaderMode{} public @interface HeaderMode {
}
public static final int POSITION_NOT_FOUND = -1; public static final int POSITION_NOT_FOUND = -1;
public static final int DEFAULT_SUGGESTION_COUNT = 2; public static final int DEFAULT_SUGGESTION_COUNT = 2;
// id namespace for different type of items. // stable id for different type of items.
private static final int NS_SPACER = 0; @VisibleForTesting
private static final int NS_ITEMS = 2000; static final int STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER = 0;
private static final int NS_SUGGESTION_CONDITION = 3000; @VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER = 1;
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONDITION_FOOTER = 2;
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONTAINER = 3;
@VisibleForTesting
static final int STABLE_ID_CONDITION_CONTAINER = 4;
private final List<Item> mItems; private final List<Item> mItems;
private final List<DashboardCategory> mCategories; private final List<DashboardCategory> mCategories;
private final List<Condition> mConditions; private final List<Condition> mConditions;
private final List<Tile> mSuggestions; private final List<Tile> mSuggestions;
private final @HeaderMode int mSuggestionConditionMode; @HeaderMode
private int mId; private final int mSuggestionConditionMode;
private DashboardData(Builder builder) { private DashboardData(Builder builder) {
mCategories = builder.mCategories; mCategories = builder.mCategories;
@@ -69,7 +79,6 @@ public class DashboardData {
mSuggestionConditionMode = builder.mSuggestionConditionMode; mSuggestionConditionMode = builder.mSuggestionConditionMode;
mItems = new ArrayList<>(); mItems = new ArrayList<>();
mId = 0;
buildItemsData(); buildItemsData();
} }
@@ -186,33 +195,18 @@ public class DashboardData {
return suggestionSize; return suggestionSize;
} }
public boolean hasMoreSuggestions() {
return mSuggestionConditionMode == HEADER_MODE_COLLAPSED && mSuggestions.size() > 0
|| mSuggestionConditionMode == HEADER_MODE_DEFAULT
&& mSuggestions.size() > DEFAULT_SUGGESTION_COUNT;
}
private void resetCount() {
mId = 0;
}
/** /**
* Count the item and add it into list when {@paramref add} is true. * Add item into list when {@paramref add} is true.
* *
* Note that {@link #mId} will increment automatically and the real * @param item maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
* id stored in {@link Item} is shifted by {@paramref nameSpace}. This is a * @param type type of the item, and value is the layout id
* simple way to keep the id stable. * @param stableId The stable id for this item
* * @param add flag about whether to add item into list
* @param object maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
* @param type type of the item, and value is the layout id
* @param add flag about whether to add item into list
* @param nameSpace namespace based on the type
*/ */
private void countItem(Object object, int type, boolean add, int nameSpace) { private void addToItemList(Object item, int type, int stableId, boolean add) {
if (add) { if (add) {
mItems.add(new Item(object, type, mId + nameSpace)); mItems.add(new Item(item, type, stableId));
} }
mId++;
} }
/** /**
@@ -228,51 +222,52 @@ public class DashboardData {
final int hiddenSuggestion = final int hiddenSuggestion =
hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0; hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;
resetCount(); /* Top suggestion/condition header. This will be present when there is any suggestion or
/* Top suggestion/condition header. This will be present when there is any suggestion or * condition to show, except in the case that there is only conditions to show and the
* condition to show, except in the case that there is only conditions to show and the * mode is fully expanded. */
* mode is fully expanded. */ addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
countItem(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
R.layout.suggestion_condition_header, hasSuggestions
|| hasConditions && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED,
NS_SUGGESTION_CONDITION);
/* Suggestion container. This is the card view that contains the list of suggestions.
* This will be added whenever the suggestion list is not empty */
countItem(suggestions, R.layout.suggestion_condition_container, sizeOf(suggestions) > 0,
NS_SUGGESTION_CONDITION);
/* Second suggestion/condition header. This will be added when there is at least one
* suggestion or condition that is not currently displayed, and the user can expand the
* section to view more items. */
countItem(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
R.layout.suggestion_condition_header, R.layout.suggestion_condition_header,
STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER,
hasSuggestions
|| hasConditions && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED);
/* Suggestion container. This is the card view that contains the list of suggestions.
* This will be added whenever the suggestion list is not empty */
addToItemList(suggestions, R.layout.suggestion_condition_container,
STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
/* Second suggestion/condition header. This will be added when there is at least one
* suggestion or condition that is not currently displayed, and the user can expand the
* section to view more items. */
addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
R.layout.suggestion_condition_header,
STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER,
mSuggestionConditionMode != HEADER_MODE_COLLAPSED mSuggestionConditionMode != HEADER_MODE_COLLAPSED
&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED
&& (hiddenSuggestion > 0 && (hiddenSuggestion > 0 || hasConditions && hasSuggestions));
|| hasConditions && hasSuggestions),
NS_SUGGESTION_CONDITION);
/* Condition container. This is the card view that contains the list of conditions. /* Condition container. This is the card view that contains the list of conditions.
* This will be added whenever the condition list is not empty */ * This will be added whenever the condition list is not empty */
countItem(conditions, R.layout.suggestion_condition_container, addToItemList(conditions, R.layout.suggestion_condition_container,
hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED, STABLE_ID_CONDITION_CONTAINER,
NS_SUGGESTION_CONDITION); hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED);
/* Suggestion/condition footer. This will be present when the section is fully expanded /* Suggestion/condition footer. This will be present when the section is fully expanded
* or when there is no conditions and no hidden suggestions */ * or when there is no conditions and no hidden suggestions */
countItem(null, R.layout.suggestion_condition_footer, addToItemList(null /* item */, R.layout.suggestion_condition_footer,
(hasConditions || hasSuggestions) && STABLE_ID_SUGGESTION_CONDITION_FOOTER,
mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED (hasConditions || hasSuggestions)
|| hasSuggestions && !hasConditions && hiddenSuggestion == 0, && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED
NS_SUGGESTION_CONDITION); || hasSuggestions
&& !hasConditions
&& hiddenSuggestion == 0);
resetCount();
for (int i = 0; mCategories != null && i < mCategories.size(); i++) { for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i); DashboardCategory category = mCategories.get(i);
for (int j = 0; j < category.tiles.size(); j++) { for (int j = 0; j < category.tiles.size(); j++) {
Tile tile = category.tiles.get(j); final Tile tile = category.tiles.get(j);
countItem(tile, R.layout.dashboard_tile, true, NS_ITEMS); addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
true /* add */);
} }
} }
} }
@@ -313,7 +308,8 @@ public class DashboardData {
* {@link #mSuggestionConditionMode} have default value while others are not. * {@link #mSuggestionConditionMode} have default value while others are not.
*/ */
public static class Builder { public static class Builder {
private @HeaderMode int mSuggestionConditionMode = HEADER_MODE_DEFAULT; @HeaderMode
private int mSuggestionConditionMode = HEADER_MODE_DEFAULT;
private List<DashboardCategory> mCategories; private List<DashboardCategory> mCategories;
private List<Condition> mConditions; private List<Condition> mConditions;
@@ -392,13 +388,13 @@ public class DashboardData {
/** /**
* An item contains the data needed in the DashboardData. * An item contains the data needed in the DashboardData.
*/ */
private static class Item { static class Item {
// valid types in field type // valid types in field type
private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile; private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
private static final int TYPE_SUGGESTION_CONDITION_CONTAINER = private static final int TYPE_SUGGESTION_CONDITION_CONTAINER =
R.layout.suggestion_condition_container; R.layout.suggestion_condition_container;
private static final int TYPE_SUGGESTION_CONDITION_HEADER = private static final int TYPE_SUGGESTION_CONDITION_HEADER =
R.layout.suggestion_condition_header; R.layout.suggestion_condition_header;
private static final int TYPE_SUGGESTION_CONDITION_FOOTER = private static final int TYPE_SUGGESTION_CONDITION_FOOTER =
R.layout.suggestion_condition_footer; R.layout.suggestion_condition_footer;
private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer; private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;
@@ -407,7 +403,8 @@ public class DashboardData {
TYPE_SUGGESTION_CONDITION_HEADER, TYPE_SUGGESTION_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_HEADER, TYPE_SUGGESTION_CONDITION_FOOTER,
TYPE_DASHBOARD_SPACER}) TYPE_DASHBOARD_SPACER})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface ItemTypes{} public @interface ItemTypes {
}
/** /**
* The main data object in item, usually is a {@link Tile}, {@link Condition} * The main data object in item, usually is a {@link Tile}, {@link Condition}
@@ -420,7 +417,8 @@ public class DashboardData {
/** /**
* The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile) * The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
*/ */
public final @ItemTypes int type; @ItemTypes
public final int type;
/** /**
* Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item. * Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
@@ -435,6 +433,7 @@ public class DashboardData {
/** /**
* Override it to make comparision in the {@link ItemsDataDiffCallback} * Override it to make comparision in the {@link ItemsDataDiffCallback}
*
* @param obj object to compared with * @param obj object to compared with
* @return true if the same object or has equal value. * @return true if the same object or has equal value.
*/ */

View File

@@ -37,7 +37,13 @@ import org.robolectric.annotation.Config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_CONDITION_CONTAINER;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONDITION_FOOTER;
import static com.android.settings.dashboard.DashboardData
.STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONTAINER;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -112,6 +118,20 @@ public class DashboardDataTest {
.build(); .build();
} }
@Test
public void testBuildItemsData_shouldSetstableId() {
final List<DashboardData.Item> items = mDashboardDataWithOneConditions.getItemList();
// Header, suggestion, condition, footer, 1 tile
assertThat(items).hasSize(5);
assertThat(items.get(0).id).isEqualTo(STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER);
assertThat(items.get(1).id).isEqualTo(STABLE_ID_SUGGESTION_CONTAINER);
assertThat(items.get(2).id).isEqualTo(STABLE_ID_CONDITION_CONTAINER);
assertThat(items.get(3).id).isEqualTo(STABLE_ID_SUGGESTION_CONDITION_FOOTER);
assertThat(items.get(4).id).isEqualTo(Objects.hash(mTestCategoryTile.title));
}
@Test @Test
public void testBuildItemsData_containsAllData() { public void testBuildItemsData_containsAllData() {
final DashboardData.SuggestionConditionHeaderData data = final DashboardData.SuggestionConditionHeaderData data =