Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a29346ba authored by Fan Zhang's avatar Fan Zhang
Browse files

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
parent 6d1bb15f
Loading
Loading
Loading
Loading
+69 −70
Original line number Original line Diff line number Diff line
@@ -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
     * id stored in {@link Item} is shifted by {@paramref nameSpace}. This is a
     * simple way to keep the id stable.
     *
     *
     * @param object    maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
     * @param item     maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
     * @param type     type of the item, and value is the layout id
     * @param type     type of the item, and value is the layout id
     * @param stableId The stable id for this item
     * @param add      flag about whether to add item into list
     * @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. */
        countItem(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
        addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
                R.layout.suggestion_condition_header, hasSuggestions
                R.layout.suggestion_condition_header,
                        || hasConditions && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED,
                STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER,
                NS_SUGGESTION_CONDITION);
                hasSuggestions
                        || hasConditions && mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED);


        /* Suggestion container. This is the card view that contains the list of suggestions.
        /* Suggestion container. This is the card view that contains the list of suggestions.
         * This will be added whenever the suggestion list is not empty */
         * This will be added whenever the suggestion list is not empty */
        countItem(suggestions, R.layout.suggestion_condition_container, sizeOf(suggestions) > 0,
        addToItemList(suggestions, R.layout.suggestion_condition_container,
                NS_SUGGESTION_CONDITION);
                STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);


        /* Second suggestion/condition header. This will be added when there is at least one
        /* 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
         * suggestion or condition that is not currently displayed, and the user can expand the
         * section to view more items. */
         * section to view more items. */
        countItem(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
        addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
                R.layout.suggestion_condition_header,
                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,7 +388,7 @@ 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 =
@@ -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.
         */
         */
+20 −0
Original line number Original line Diff line number Diff line
@@ -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 =