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:
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
@@ -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 =
|
||||||
|
Reference in New Issue
Block a user