Build a way to decide card width

Slice cards in contextual homepage are shown as half card or full card.
We will use Category field of ContextualCard to decide card width.

In this CL, also fixed the problem of not showing full card after a
consecutive suggestion card dismissal.

Bug: 119655434
Bug: 121315057
Test: visual, robotest
Change-Id: I3243b9db21b8f288cab88238b20d7d50a2a20d46
This commit is contained in:
Mill Chen
2018-12-12 00:31:33 +08:00
parent 06c514db6f
commit 030aadad10
4 changed files with 206 additions and 15 deletions

View File

@@ -107,12 +107,12 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
} }
} }
} }
return getFinalDisplayableCards(result); return getDisplayableCards(result);
} }
// Get final displayed cards and log what cards will be displayed/hidden // Get final displayed cards and log what cards will be displayed/hidden
@VisibleForTesting @VisibleForTesting
List<ContextualCard> getFinalDisplayableCards(List<ContextualCard> candidates) { List<ContextualCard> getDisplayableCards(List<ContextualCard> candidates) {
final List<ContextualCard> eligibleCards = filterEligibleCards(candidates); final List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
final List<ContextualCard> visibleCards = new ArrayList<>(); final List<ContextualCard> visibleCards = new ArrayList<>();
final List<ContextualCard> hiddenCards = new ArrayList<>(); final List<ContextualCard> hiddenCards = new ArrayList<>();

View File

@@ -17,6 +17,7 @@
package com.android.settings.homepage.contextualcards; package com.android.settings.homepage.contextualcards;
import static com.android.settings.homepage.contextualcards.ContextualCardLoader.CARD_CONTENT_LOADER_ID; import static com.android.settings.homepage.contextualcards.ContextualCardLoader.CARD_CONTENT_LOADER_ID;
import static com.android.settings.intelligence.ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE;
import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingBy;
@@ -172,7 +173,8 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
//replace with the new data //replace with the new data
mContextualCards.clear(); mContextualCards.clear();
mContextualCards.addAll(sortCards(allCards)); final List<ContextualCard> sortedCards = sortCards(allCards);
mContextualCards.addAll(assignCardWidth(sortedCards));
loadCardControllers(); loadCardControllers();
@@ -224,6 +226,24 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
mListener = listener; mListener = listener;
} }
@VisibleForTesting
List<ContextualCard> assignCardWidth(List<ContextualCard> cards) {
final List<ContextualCard> result = new ArrayList<>(cards);
// Shows as half cards if 2 suggestion type of cards are next to each other.
// Shows as full card if 1 suggestion type of card lives alone.
for (int index = 1; index < result.size(); index++) {
final ContextualCard previous = result.get(index - 1);
final ContextualCard current = result.get(index);
if (current.getCategory() == SUGGESTION_VALUE
&& previous.getCategory() == SUGGESTION_VALUE) {
result.set(index - 1, previous.mutate().setIsHalfWidth(true).build());
result.set(index, current.mutate().setIsHalfWidth(true).build());
index++;
}
}
return result;
}
private List<ContextualCard> getCardsToKeep(List<ContextualCard> cards) { private List<ContextualCard> getCardsToKeep(List<ContextualCard> cards) {
if (mSavedCards != null) { if (mSavedCards != null) {
//screen rotate //screen rotate

View File

@@ -29,16 +29,16 @@ import android.net.Uri;
import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.CustomSliceRegistry;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class ContextualCardLoaderTest { public class ContextualCardLoaderTest {
@@ -82,29 +82,29 @@ public class ContextualCardLoaderTest {
} }
@Test @Test
public void getFinalDisplayableCards_twoEligibleCards_shouldShowAll() { public void getDisplayableCards_twoEligibleCards_shouldShowAll() {
final List<ContextualCard> cards = getContextualCardList().stream().limit(2) final List<ContextualCard> cards = getContextualCardList().stream().limit(2)
.collect(Collectors.toList()); .collect(Collectors.toList());
doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(cards); final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(cards);
assertThat(result).hasSize(cards.size()); assertThat(result).hasSize(cards.size());
} }
@Test @Test
public void getFinalDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() { public void getDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() {
final List<ContextualCard> fiveCards = getContextualCardListWithNoLargeCard(); final List<ContextualCard> fiveCards = getContextualCardListWithNoLargeCard();
doReturn(fiveCards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); doReturn(fiveCards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards( final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(
fiveCards); fiveCards);
assertThat(result).hasSize(DEFAULT_CARD_COUNT); assertThat(result).hasSize(DEFAULT_CARD_COUNT);
} }
@Test @Test
public void getFinalDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() { public void getDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() {
final List<ContextualCard> cards = getContextualCardList().stream().limit(2) final List<ContextualCard> cards = getContextualCardList().stream().limit(2)
.collect(Collectors.toList()); .collect(Collectors.toList());
cards.add(new ContextualCard.Builder() cards.add(new ContextualCard.Builder()
@@ -115,18 +115,18 @@ public class ContextualCardLoaderTest {
.build()); .build());
doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(cards); final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(cards);
assertThat(result).hasSize(3); assertThat(result).hasSize(3);
} }
@Test @Test
public void getFinalDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() { public void getDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() {
final List<ContextualCard> threeCards = getContextualCardList().stream().limit(3) final List<ContextualCard> threeCards = getContextualCardList().stream().limit(3)
.collect(Collectors.toList()); .collect(Collectors.toList());
doReturn(threeCards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); doReturn(threeCards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards( final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(
threeCards); threeCards);
assertThat(result).hasSize(2); assertThat(result).hasSize(2);

View File

@@ -32,6 +32,8 @@ import android.util.ArrayMap;
import com.android.settings.homepage.contextualcards.conditional.ConditionFooterContextualCard; import com.android.settings.homepage.contextualcards.conditional.ConditionFooterContextualCard;
import com.android.settings.homepage.contextualcards.conditional.ConditionHeaderContextualCard; import com.android.settings.homepage.contextualcards.conditional.ConditionHeaderContextualCard;
import com.android.settings.homepage.contextualcards.conditional.ConditionalContextualCard; import com.android.settings.homepage.contextualcards.conditional.ConditionalContextualCard;
import com.android.settings.intelligence.ContextualCardProto;
import com.android.settings.slices.CustomSliceRegistry;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -203,6 +205,134 @@ public class ContextualCardManagerTest {
assertThat(actualCards).containsExactlyElementsIn(expectedCards); assertThat(actualCards).containsExactlyElementsIn(expectedCards);
} }
@Test
public void assignCardWidth_noSuggestionCards_shouldNotHaveHalfCards() {
final List<Integer> categories = Arrays.asList(
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE
);
final List<ContextualCard> noSuggestionCards = buildCategoriedCards(getContextualCardList(),
categories);
final List<ContextualCard> result = mManager.assignCardWidth(noSuggestionCards);
assertThat(result).hasSize(5);
for (ContextualCard card : result) {
assertThat(card.isHalfWidth()).isFalse();
}
}
@Test
public void assignCardWidth_oneSuggestionCards_shouldNotHaveHalfCards() {
final List<Integer> categories = Arrays.asList(
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
);
final List<ContextualCard> oneSuggestionCards = buildCategoriedCards(
getContextualCardList(), categories);
final List<ContextualCard> result = mManager.assignCardWidth(oneSuggestionCards);
assertThat(result).hasSize(5);
for (ContextualCard card : result) {
assertThat(card.isHalfWidth()).isFalse();
}
}
@Test
public void assignCardWidth_twoConsecutiveSuggestionCards_shouldHaveTwoHalfCards() {
final List<Integer> categories = Arrays.asList(
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE
);
final List<ContextualCard> twoConsecutiveSuggestionCards = buildCategoriedCards(
getContextualCardList(), categories);
final List<Boolean> expectedValues = Arrays.asList(false, false, true, true, false);
final List<ContextualCard> result = mManager.assignCardWidth(
twoConsecutiveSuggestionCards);
assertThat(result).hasSize(5);
for (int i = 0; i < result.size(); i++) {
assertThat(result.get(i).isHalfWidth()).isEqualTo(expectedValues.get(i));
}
}
@Test
public void assignCardWidth_twoNonConsecutiveSuggestionCards_shouldNotHaveHalfCards() {
final List<Integer> categories = Arrays.asList(
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
);
final List<ContextualCard> twoNonConsecutiveSuggestionCards = buildCategoriedCards(
getContextualCardList(), categories);
final List<ContextualCard> result = mManager.assignCardWidth(
twoNonConsecutiveSuggestionCards);
assertThat(result).hasSize(5);
for (ContextualCard card : result) {
assertThat(card.isHalfWidth()).isFalse();
}
}
@Test
public void assignCardWidth_threeConsecutiveSuggestionCards_shouldHaveTwoHalfCards() {
final List<Integer> categories = Arrays.asList(
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE
);
final List<ContextualCard> threeConsecutiveSuggestionCards = buildCategoriedCards(
getContextualCardList(), categories);
final List<Boolean> expectedValues = Arrays.asList(false, true, true, false, false);
final List<ContextualCard> result = mManager.assignCardWidth(
threeConsecutiveSuggestionCards);
assertThat(result).hasSize(5);
for (int i = 0; i < result.size(); i++) {
assertThat(result.get(i).isHalfWidth()).isEqualTo(expectedValues.get(i));
}
}
@Test
public void assignCardWidth_fourConsecutiveSuggestionCards_shouldHaveFourHalfCards() {
final List<Integer> categories = Arrays.asList(
ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
);
final List<ContextualCard> fourConsecutiveSuggestionCards = buildCategoriedCards(
getContextualCardList(), categories);
final List<Boolean> expectedValues = Arrays.asList(false, true, true, true, true);
final List<ContextualCard> result = mManager.assignCardWidth(
fourConsecutiveSuggestionCards);
assertThat(result).hasSize(5);
for (int i = 0; i < result.size(); i++) {
assertThat(result.get(i).isHalfWidth()).isEqualTo(expectedValues.get(i));
}
}
private ContextualCard buildContextualCard(String sliceUri) { private ContextualCard buildContextualCard(String sliceUri) {
return new ContextualCard.Builder() return new ContextualCard.Builder()
.setName(TEST_SLICE_NAME) .setName(TEST_SLICE_NAME)
@@ -210,4 +340,45 @@ public class ContextualCardManagerTest {
.setSliceUri(Uri.parse(sliceUri)) .setSliceUri(Uri.parse(sliceUri))
.build(); .build();
} }
private List<ContextualCard> buildCategoriedCards(List<ContextualCard> cards,
List<Integer> categories) {
final List<ContextualCard> result = new ArrayList<>();
for (int i = 0; i < cards.size(); i++) {
result.add(cards.get(i).mutate().setCategory(categories.get(i)).build());
}
return result;
}
private List<ContextualCard> getContextualCardList() {
final List<ContextualCard> cards = new ArrayList<>();
cards.add(new ContextualCard.Builder()
.setName("test_wifi")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI)
.build());
cards.add(new ContextualCard.Builder()
.setName("test_flashlight")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(
Uri.parse("content://com.android.settings.test.slices/action/flashlight"))
.build());
cards.add(new ContextualCard.Builder()
.setName("test_connected")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI)
.build());
cards.add(new ContextualCard.Builder()
.setName("test_gesture")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(Uri.parse(
"content://com.android.settings.test.slices/action/gesture_pick_up"))
.build());
cards.add(new ContextualCard.Builder()
.setName("test_battery")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(CustomSliceRegistry.BATTERY_INFO_SLICE_URI)
.build());
return cards;
}
} }