From 030aadad103480da3e2462c602ce48408b48e141 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Wed, 12 Dec 2018 00:31:33 +0800 Subject: [PATCH 1/9] 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 --- .../contextualcards/ContextualCardLoader.java | 4 +- .../ContextualCardManager.java | 22 ++- .../ContextualCardLoaderTest.java | 24 +-- .../ContextualCardManagerTest.java | 171 ++++++++++++++++++ 4 files changed, 206 insertions(+), 15 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java index 88478e3d065..49e2a7697f6 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java @@ -107,12 +107,12 @@ public class ContextualCardLoader extends AsyncLoaderCompat } } } - return getFinalDisplayableCards(result); + return getDisplayableCards(result); } // Get final displayed cards and log what cards will be displayed/hidden @VisibleForTesting - List getFinalDisplayableCards(List candidates) { + List getDisplayableCards(List candidates) { final List eligibleCards = filterEligibleCards(candidates); final List visibleCards = new ArrayList<>(); final List hiddenCards = new ArrayList<>(); diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java index 067de7ccf3a..12088f8a106 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java @@ -17,6 +17,7 @@ package com.android.settings.homepage.contextualcards; 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; @@ -172,7 +173,8 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo //replace with the new data mContextualCards.clear(); - mContextualCards.addAll(sortCards(allCards)); + final List sortedCards = sortCards(allCards); + mContextualCards.addAll(assignCardWidth(sortedCards)); loadCardControllers(); @@ -224,6 +226,24 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo mListener = listener; } + @VisibleForTesting + List assignCardWidth(List cards) { + final List 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 getCardsToKeep(List cards) { if (mSavedCards != null) { //screen rotate diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java index c62a6bbbfdb..c47fa38868d 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java @@ -29,16 +29,16 @@ import android.net.Uri; 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.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + @RunWith(RobolectricTestRunner.class) public class ContextualCardLoaderTest { @@ -82,29 +82,29 @@ public class ContextualCardLoaderTest { } @Test - public void getFinalDisplayableCards_twoEligibleCards_shouldShowAll() { + public void getDisplayableCards_twoEligibleCards_shouldShowAll() { final List cards = getContextualCardList().stream().limit(2) .collect(Collectors.toList()); doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); - final List result = mContextualCardLoader.getFinalDisplayableCards(cards); + final List result = mContextualCardLoader.getDisplayableCards(cards); assertThat(result).hasSize(cards.size()); } @Test - public void getFinalDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() { + public void getDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() { final List fiveCards = getContextualCardListWithNoLargeCard(); doReturn(fiveCards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); - final List result = mContextualCardLoader.getFinalDisplayableCards( + final List result = mContextualCardLoader.getDisplayableCards( fiveCards); assertThat(result).hasSize(DEFAULT_CARD_COUNT); } @Test - public void getFinalDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() { + public void getDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() { final List cards = getContextualCardList().stream().limit(2) .collect(Collectors.toList()); cards.add(new ContextualCard.Builder() @@ -115,18 +115,18 @@ public class ContextualCardLoaderTest { .build()); doReturn(cards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); - final List result = mContextualCardLoader.getFinalDisplayableCards(cards); + final List result = mContextualCardLoader.getDisplayableCards(cards); assertThat(result).hasSize(3); } @Test - public void getFinalDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() { + public void getDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() { final List threeCards = getContextualCardList().stream().limit(3) .collect(Collectors.toList()); doReturn(threeCards).when(mContextualCardLoader).filterEligibleCards(any(List.class)); - final List result = mContextualCardLoader.getFinalDisplayableCards( + final List result = mContextualCardLoader.getDisplayableCards( threeCards); assertThat(result).hasSize(2); diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java index c405ffc4f1b..3fc8e060456 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java @@ -32,6 +32,8 @@ import android.util.ArrayMap; import com.android.settings.homepage.contextualcards.conditional.ConditionFooterContextualCard; import com.android.settings.homepage.contextualcards.conditional.ConditionHeaderContextualCard; 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.Test; @@ -203,6 +205,134 @@ public class ContextualCardManagerTest { assertThat(actualCards).containsExactlyElementsIn(expectedCards); } + + @Test + public void assignCardWidth_noSuggestionCards_shouldNotHaveHalfCards() { + final List 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 noSuggestionCards = buildCategoriedCards(getContextualCardList(), + categories); + + final List result = mManager.assignCardWidth(noSuggestionCards); + + assertThat(result).hasSize(5); + for (ContextualCard card : result) { + assertThat(card.isHalfWidth()).isFalse(); + } + } + + @Test + public void assignCardWidth_oneSuggestionCards_shouldNotHaveHalfCards() { + final List 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 oneSuggestionCards = buildCategoriedCards( + getContextualCardList(), categories); + + final List result = mManager.assignCardWidth(oneSuggestionCards); + + assertThat(result).hasSize(5); + for (ContextualCard card : result) { + assertThat(card.isHalfWidth()).isFalse(); + } + } + + @Test + public void assignCardWidth_twoConsecutiveSuggestionCards_shouldHaveTwoHalfCards() { + final List 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 twoConsecutiveSuggestionCards = buildCategoriedCards( + getContextualCardList(), categories); + final List expectedValues = Arrays.asList(false, false, true, true, false); + + final List 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 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 twoNonConsecutiveSuggestionCards = buildCategoriedCards( + getContextualCardList(), categories); + + final List result = mManager.assignCardWidth( + twoNonConsecutiveSuggestionCards); + + assertThat(result).hasSize(5); + for (ContextualCard card : result) { + assertThat(card.isHalfWidth()).isFalse(); + } + } + + @Test + public void assignCardWidth_threeConsecutiveSuggestionCards_shouldHaveTwoHalfCards() { + final List 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 threeConsecutiveSuggestionCards = buildCategoriedCards( + getContextualCardList(), categories); + final List expectedValues = Arrays.asList(false, true, true, false, false); + + final List 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 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 fourConsecutiveSuggestionCards = buildCategoriedCards( + getContextualCardList(), categories); + final List expectedValues = Arrays.asList(false, true, true, true, true); + + final List 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) { return new ContextualCard.Builder() .setName(TEST_SLICE_NAME) @@ -210,4 +340,45 @@ public class ContextualCardManagerTest { .setSliceUri(Uri.parse(sliceUri)) .build(); } + + private List buildCategoriedCards(List cards, + List categories) { + final List 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 getContextualCardList() { + final List 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; + } } From b5ce3cd9b003d8235c04ebae019769aac6010b59 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Wed, 19 Dec 2018 14:52:42 -0800 Subject: [PATCH 2/9] Fix TODO for mobile network settings 1. Add intent filter to MobileNetworkActivity(new page) 2. Add metrics id 3. Remove obsolete TODOs Bug: 114749736 Test: Manual Change-Id: I36f41983dc8cc36ccdf548174fc494044ec1b241 --- AndroidManifest.xml | 6 +++++- .../network/telephony/MobileDataDialogFragment.java | 4 ++-- .../settings/network/telephony/MobileNetworkSettings.java | 1 - .../telephony/PreferredNetworkModePreferenceController.java | 2 -- .../settings/network/telephony/RoamingDialogFragment.java | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 871f6092ca7..de2d9a1a891 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -139,7 +139,11 @@ android:label="@string/network_settings_title" android:theme="@style/Theme.Settings.Home" android:launchMode="singleTask"> - + + + + + diff --git a/src/com/android/settings/network/telephony/MobileDataDialogFragment.java b/src/com/android/settings/network/telephony/MobileDataDialogFragment.java index be2da048828..276d1fbe613 100644 --- a/src/com/android/settings/network/telephony/MobileDataDialogFragment.java +++ b/src/com/android/settings/network/telephony/MobileDataDialogFragment.java @@ -17,6 +17,7 @@ package com.android.settings.network.telephony; import android.app.Dialog; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; @@ -109,8 +110,7 @@ public class MobileDataDialogFragment extends InstrumentedDialogFragment impleme @Override public int getMetricsCategory() { - //TODO(b/114749736): add metric id for this fragment - return 0; + return SettingsEnums.MOBILE_DATA_DIALOG; } @Override diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index 623b6de9920..6e5dece5a01 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -196,7 +196,6 @@ public class MobileNetworkSettings extends RestrictedDashboardFragment { } } - //TODO(b/114749736): update search provider public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override diff --git a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java index f60f927a32e..0326f42d8c6 100644 --- a/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java +++ b/src/com/android/settings/network/telephony/PreferredNetworkModePreferenceController.java @@ -116,8 +116,6 @@ public class PreferredNetworkModePreferenceController extends BasePreferenceCont } private int getPreferredNetworkModeSummaryResId(int NetworkMode) { - //TODO(b/114749736): refactor it to "Preferred network mode: ", instead of building - // string for each type... switch (NetworkMode) { case TelephonyManager.NETWORK_MODE_TDSCDMA_GSM_WCDMA: return R.string.preferred_network_mode_tdscdma_gsm_wcdma_summary; diff --git a/src/com/android/settings/network/telephony/RoamingDialogFragment.java b/src/com/android/settings/network/telephony/RoamingDialogFragment.java index 4c82686081a..c349c1a47fa 100644 --- a/src/com/android/settings/network/telephony/RoamingDialogFragment.java +++ b/src/com/android/settings/network/telephony/RoamingDialogFragment.java @@ -17,6 +17,7 @@ package com.android.settings.network.telephony; import android.app.AlertDialog; import android.app.Dialog; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; @@ -78,8 +79,7 @@ public class RoamingDialogFragment extends InstrumentedDialogFragment implements @Override public int getMetricsCategory() { - //TODO(b/114749736): add category for roaming dialog - return 0; + return SettingsEnums.MOBILE_ROAMING_DIALOG; } @Override From 8a1bccbc30bfd48199890becdc683bd90f539261 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Fri, 4 Jan 2019 21:28:20 +0800 Subject: [PATCH 3/9] Improve visual - Collapse the conditionals in all cases Changing the threshold value to make collapsing mechanism of conditionals work in all cases, where the threshold value is changed to 0 from 2. Bug: 122310542 Test: robotests Change-Id: I227114acdc6770baa0c133397d08e3ad77f6c572 --- .../ConditionContextualCardController.java | 2 +- ...ConditionContextualCardControllerTest.java | 30 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java index 834ebbc6539..b477d5229cd 100644 --- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java +++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardController.java @@ -40,7 +40,7 @@ import java.util.stream.Collectors; */ public class ConditionContextualCardController implements ContextualCardController, ConditionListener, LifecycleObserver, OnStart, OnStop { - public static final int EXPANDING_THRESHOLD = 2; + public static final int EXPANDING_THRESHOLD = 0; private static final double UNSUPPORTED_RANKING = -99999.0; private static final String TAG = "ConditionCtxCardCtrl"; diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java index 566ca07a46c..4553f7ce146 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/ConditionContextualCardControllerTest.java @@ -112,7 +112,8 @@ public class ConditionContextualCardControllerTest { } @Test - public void getConditionalCards_hasOneConditionCard_shouldGetOneFullWidthCard() { + public void getConditionalCards_hasOneConditionCardAndExpanded_shouldGetOneFullWidthCard() { + mController.setIsExpanded(true); final Map> conditionalCards = mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(1)); @@ -120,11 +121,24 @@ public class ConditionContextualCardControllerTest { assertThat(conditionalCards.get(CardType.CONDITIONAL)).hasSize(1); assertThat(conditionalCards.get(CardType.CONDITIONAL).get(0).isHalfWidth()).isFalse(); assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isEmpty(); + assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isNotEmpty(); + } + + @Test + public void getConditionalCards_hasOneConditionCardAndCollapsed_shouldGetConditionalHeader() { + mController.setIsExpanded(false); + final Map> conditionalCards = + mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(1)); + + assertThat(conditionalCards).hasSize(3); + assertThat(conditionalCards.get(CardType.CONDITIONAL)).isEmpty(); + assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isNotEmpty(); assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isEmpty(); } @Test - public void getConditionalCards_hasTwoConditionCards_shouldGetTwoHalfWidthCards() { + public void getConditionalCards_hasTwoConditionCardsAndExpanded_shouldGetTwoHalfWidthCards() { + mController.setIsExpanded(true); final Map> conditionalCards = mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(2)); @@ -134,6 +148,18 @@ public class ConditionContextualCardControllerTest { assertThat(card.isHalfWidth()).isTrue(); } assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isEmpty(); + assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isNotEmpty(); + } + + @Test + public void getConditionalCards_hasTwoConditionCardsAndCollapsed_shouldGetConditionalHeader() { + mController.setIsExpanded(false); + final Map> conditionalCards = + mController.buildConditionalCardsWithFooterOrHeader(generateConditionCards(2)); + + assertThat(conditionalCards).hasSize(3); + assertThat(conditionalCards.get(CardType.CONDITIONAL)).isEmpty(); + assertThat(conditionalCards.get(CardType.CONDITIONAL_HEADER)).isNotEmpty(); assertThat(conditionalCards.get(CardType.CONDITIONAL_FOOTER)).isEmpty(); } From 497b3529dc3b76ff5c2f8377986548de8ffd74b5 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Wed, 19 Dec 2018 00:07:17 +0800 Subject: [PATCH 4/9] Refactor slice renderer to handle different card width - Refactor SliceContextualCardRenderer to support for displaying slice in half/full width card. - Add two helper classes to separately deal with different card width. Only the skeleton of the half card helper is put in this CL, the implementation hasn't been filled in yet. Will implement the detail in next CL. Bug: 119655434 Test: visual, robotests Change-Id: Iacdc90c23bf41cfa7ccae3c0c70a3b663e89307d --- .../ContextualCardLookupTable.java | 6 +- .../slices/SliceContextualCardRenderer.java | 120 ++++++-------- .../slices/SliceFullCardRendererHelper.java | 99 ++++++++++++ .../slices/SliceHalfCardRendererHelper.java | 46 ++++++ .../SliceContextualCardRendererTest.java | 13 +- .../SliceFullCardRendererHelperTest.java | 151 ++++++++++++++++++ 6 files changed, 350 insertions(+), 85 deletions(-) create mode 100644 src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java create mode 100644 src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java index a4a84199c98..90974f651bb 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLookupTable.java @@ -84,7 +84,11 @@ public class ContextualCardLookupTable { LegacySuggestionContextualCardController.class, LegacySuggestionContextualCardRenderer.class)); add(new ControllerRendererMapping(CardType.SLICE, - SliceContextualCardRenderer.VIEW_TYPE, + SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, + SliceContextualCardController.class, + SliceContextualCardRenderer.class)); + add(new ControllerRendererMapping(CardType.SLICE, + SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH, SliceContextualCardController.class, SliceContextualCardRenderer.class)); add(new ControllerRendererMapping(CardType.CONDITIONAL_FOOTER, diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java index 7ca6e9aad84..5a43f66241b 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java @@ -26,7 +26,6 @@ import android.view.View; import android.widget.Button; import android.widget.ViewFlipper; -import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; @@ -35,40 +34,40 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.OnLifecycleEvent; import androidx.recyclerview.widget.RecyclerView; import androidx.slice.Slice; -import androidx.slice.SliceItem; -import androidx.slice.widget.EventInfo; import androidx.slice.widget.SliceLiveData; -import androidx.slice.widget.SliceView; import com.android.settings.R; import com.android.settings.homepage.contextualcards.CardContentProvider; import com.android.settings.homepage.contextualcards.ContextualCard; -import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; import com.android.settings.homepage.contextualcards.ContextualCardRenderer; import com.android.settings.homepage.contextualcards.ControllerRendererPool; -import com.android.settings.overlay.FeatureFactory; import java.util.Map; import java.util.Set; /** - * Card renderer for {@link ContextualCard} built as slices. + * Card renderer for {@link ContextualCard} built as slice full card or slice half card. */ -public class SliceContextualCardRenderer implements ContextualCardRenderer, - SliceView.OnSliceActionListener, LifecycleObserver { - public static final int VIEW_TYPE = R.layout.homepage_slice_tile; +public class SliceContextualCardRenderer implements ContextualCardRenderer, LifecycleObserver { + public static final int VIEW_TYPE_FULL_WIDTH = R.layout.homepage_slice_tile; + public static final int VIEW_TYPE_HALF_WIDTH = R.layout.homepage_slice_half_tile; private static final String TAG = "SliceCardRenderer"; @VisibleForTesting final Map> mSliceLiveDataMap; @VisibleForTesting - final Set mFlippedCardSet; + final Set mFlippedCardSet; private final Context mContext; private final LifecycleOwner mLifecycleOwner; private final ControllerRendererPool mControllerRendererPool; private final Set mCardSet; + private final SliceFullCardRendererHelper mFullCardHelper; + private final SliceHalfCardRendererHelper mHalfCardHelper; + + //TODO(b/121303357): Remove isHalfWidth field from SliceContextualCardRenderer class. + private boolean mIsHalfWidth; public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner, ControllerRendererPool controllerRendererPool) { @@ -79,21 +78,26 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, mCardSet = new ArraySet<>(); mFlippedCardSet = new ArraySet<>(); mLifecycleOwner.getLifecycle().addObserver(this); + mFullCardHelper = new SliceFullCardRendererHelper(context); + mHalfCardHelper = new SliceHalfCardRendererHelper(context); } @Override public int getViewType(boolean isHalfWidth) { - return VIEW_TYPE; + mIsHalfWidth = isHalfWidth; + return isHalfWidth? VIEW_TYPE_HALF_WIDTH : VIEW_TYPE_FULL_WIDTH; } @Override public RecyclerView.ViewHolder createViewHolder(View view) { - return new SliceViewHolder(view); + if (mIsHalfWidth) { + return mHalfCardHelper.createViewHolder(view); + } + return mFullCardHelper.createViewHolder(view); } @Override public void bindView(RecyclerView.ViewHolder holder, ContextualCard card) { - final SliceViewHolder cardHolder = (SliceViewHolder) holder; final Uri uri = card.getSliceUri(); //TODO(b/120629936): Take this out once blank card issue is fixed. Log.d(TAG, "bindView - uri = " + uri); @@ -103,10 +107,6 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, return; } - cardHolder.sliceView.setScrollable(false); - cardHolder.sliceView.setTag(uri); - //TODO(b/114009676): We will soon have a field to decide what slice mode we should set. - cardHolder.sliceView.setMode(SliceView.MODE_LARGE); LiveData sliceLiveData = mSliceLiveDataMap.get(uri); if (sliceLiveData == null) { @@ -125,82 +125,58 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, //TODO(b/120629936): Take this out once blank card issue is fixed. Log.d(TAG, "Slice callback - uri = " + slice.getUri()); } - cardHolder.sliceView.setSlice(slice); + if (holder.getItemViewType() == VIEW_TYPE_HALF_WIDTH) { + mHalfCardHelper.bindView(holder, card, slice); + } else { + mFullCardHelper.bindView(holder, card, slice, mCardSet); + } }); - // Set this listener so we can log the interaction users make on the slice - cardHolder.sliceView.setOnSliceActionListener(this); - - // Customize slice view for Settings - cardHolder.sliceView.showTitleItems(true); - if (card.isLargeCard()) { - cardHolder.sliceView.showHeaderDivider(true); - cardHolder.sliceView.showActionDividers(true); + if (holder.getItemViewType() == VIEW_TYPE_HALF_WIDTH) { + initDismissalActions(holder, card, R.id.content); + } else { + initDismissalActions(holder, card, R.id.slice_view); } - - initDismissalActions(cardHolder, card); } - private void initDismissalActions(SliceViewHolder cardHolder, ContextualCard card) { - cardHolder.sliceView.setOnLongClickListener(v -> { - cardHolder.viewFlipper.showNext(); - mFlippedCardSet.add(cardHolder); + private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card, + int initialViewId) { + // initialView is the first view in the ViewFlipper. + final View initialView = holder.itemView.findViewById(initialViewId); + initialView.setOnLongClickListener(v -> { + flipCardToDismissalView(holder); + mFlippedCardSet.add(holder); return true; }); - final Button btnKeep = cardHolder.itemView.findViewById(R.id.keep); + final Button btnKeep = holder.itemView.findViewById(R.id.keep); btnKeep.setOnClickListener(v -> { - cardHolder.resetCard(); - mFlippedCardSet.remove(cardHolder); + mFlippedCardSet.remove(holder); + resetCardView(holder); }); - final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove); + final Button btnRemove = holder.itemView.findViewById(R.id.remove); btnRemove.setOnClickListener(v -> { mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card); - cardHolder.resetCard(); - mFlippedCardSet.remove(cardHolder); + mFlippedCardSet.remove(holder); + resetCardView(holder); mSliceLiveDataMap.get(card.getSliceUri()).removeObservers(mLifecycleOwner); }); } - @Override - public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { - //TODO(b/79698338): Log user interaction - - // sliceItem.getSlice().getUri() is like - // content://android.settings.slices/action/wifi/_gen/0/_gen/0 - // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri() - for (ContextualCard card : mCardSet) { - if (sliceItem.getSlice().getUri().toString().startsWith( - card.getSliceUri().toString())) { - ContextualCardFeatureProvider contexualCardFeatureProvider = - FeatureFactory.getFactory(mContext) - .getContextualCardFeatureProvider(mContext); - contexualCardFeatureProvider.logContextualCardClick(card, - eventInfo.rowIndex, eventInfo.actionType); - break; - } - } - } - @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onStop() { - mFlippedCardSet.stream().forEach(holder -> holder.resetCard()); + mFlippedCardSet.stream().forEach(holder -> resetCardView(holder)); mFlippedCardSet.clear(); } - public static class SliceViewHolder extends RecyclerView.ViewHolder { - public final SliceView sliceView; - public final ViewFlipper viewFlipper; + private void resetCardView(RecyclerView.ViewHolder holder) { + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.setDisplayedChild(0 /* whichChild */); + } - public SliceViewHolder(View view) { - super(view); - sliceView = view.findViewById(R.id.slice_view); - viewFlipper = view.findViewById(R.id.view_flipper); - } - - public void resetCard() { - viewFlipper.setDisplayedChild(0); - } + private void flipCardToDismissalView(RecyclerView.ViewHolder holder) { + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.showNext(); } } diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java new file mode 100644 index 00000000000..ef0a67d0e20 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.homepage.contextualcards.slices; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.widget.EventInfo; +import androidx.slice.widget.SliceView; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; +import com.android.settings.overlay.FeatureFactory; + +import java.util.Set; + +/** + * Card renderer helper for {@link ContextualCard} built as slice full card. + */ +class SliceFullCardRendererHelper implements SliceView.OnSliceActionListener { + private static final String TAG = "SliceFCRendererHelper"; + + private final Context mContext; + + private Set mCardSet; + + SliceFullCardRendererHelper(Context context) { + mContext = context; + } + + RecyclerView.ViewHolder createViewHolder(View view) { + return new SliceViewHolder(view); + } + + void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice, + Set cardSet) { + final SliceViewHolder cardHolder = (SliceViewHolder) holder; + cardHolder.sliceView.setScrollable(false); + cardHolder.sliceView.setTag(card.getSliceUri()); + //TODO(b/114009676): We will soon have a field to decide what slice mode we should set. + cardHolder.sliceView.setMode(SliceView.MODE_LARGE); + cardHolder.sliceView.setSlice(slice); + mCardSet = cardSet; + // Set this listener so we can log the interaction users make on the slice + cardHolder.sliceView.setOnSliceActionListener(this); + + // Customize slice view for Settings + cardHolder.sliceView.showTitleItems(true); + if (card.isLargeCard()) { + cardHolder.sliceView.showHeaderDivider(true); + cardHolder.sliceView.showActionDividers(true); + } + } + + @Override + public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { + // sliceItem.getSlice().getUri() is like + // content://android.settings.slices/action/wifi/_gen/0/_gen/0 + // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri() + final ContextualCardFeatureProvider contextualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext); + for (ContextualCard card : mCardSet) { + if (sliceItem.getSlice().getUri().toString().startsWith( + card.getSliceUri().toString())) { + contextualCardFeatureProvider.logContextualCardClick(card, eventInfo.rowIndex, + eventInfo.actionType); + break; + } + } + } + + static class SliceViewHolder extends RecyclerView.ViewHolder { + public final SliceView sliceView; + + public SliceViewHolder(View view) { + super(view); + sliceView = view.findViewById(R.id.slice_view); + } + } +} diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java new file mode 100644 index 00000000000..2986dbc8fe7 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.homepage.contextualcards.slices; + +import android.content.Context; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; + +import com.android.settings.homepage.contextualcards.ContextualCard; + +/** + * Card renderer helper for {@link ContextualCard} built as slice half card. + */ +class SliceHalfCardRendererHelper { + private static final String TAG = "SliceHCRendererHelper"; + + private final Context mContext; + + SliceHalfCardRendererHelper(Context context) { + mContext = context; + } + + RecyclerView.ViewHolder createViewHolder(View view) { + return null; + } + + void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice) { + + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java index 0b87525b6bd..4d9a21db9a6 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java @@ -78,17 +78,6 @@ public class SliceContextualCardRendererTest { mControllerRendererPool); } - @Test - public void bindView_shouldSetScrollableToFalse() { - RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); - - assertThat( - ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.isScrollable - ()).isFalse(); - } - @Test public void bindView_invalidScheme_sliceShouldBeNull() { final Uri sliceUri = Uri.parse("contet://com.android.settings.slices/action/flashlight"); @@ -97,7 +86,7 @@ public class SliceContextualCardRendererTest { mRenderer.bindView(viewHolder, buildContextualCard(sliceUri)); assertThat( - ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.getSlice()) + ((SliceFullCardRendererHelper.SliceViewHolder) viewHolder).sliceView.getSlice()) .isNull(); } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java new file mode 100644 index 00000000000..9172300df44 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.homepage.contextualcards.slices; + +import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; +import androidx.slice.widget.SliceLiveData; +import androidx.slice.widget.SliceView; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder; +import com.android.settings.intelligence.ContextualCardProto; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class SliceFullCardRendererHelperTest { + + private static final Uri TEST_SLICE_URI = Uri.parse("content://test/test"); + + private Activity mActivity; + private SliceFullCardRendererHelper mHelper; + + @Before + public void setUp() { + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mActivity = Robolectric.buildActivity(Activity.class).create().get(); + mActivity.setTheme(R.style.Theme_Settings_Home); + mHelper = new SliceFullCardRendererHelper(mActivity); + } + + @Test + public void createViewHolder_shouldAlwaysReturnSliceViewHolder() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + assertThat(viewHolder).isInstanceOf(SliceViewHolder.class); + } + + @Test + public void bindView_shouldSetScrollableToFalse() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.isScrollable()).isFalse(); + } + + @Test + public void bindView_shouldSetTagToSliceUri() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + final ContextualCard card = buildContextualCard(); + + mHelper.bindView(viewHolder, card, buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.getTag()).isEqualTo(card.getSliceUri()); + } + + @Test + public void bindView_shouldSetModeToLarge() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.getMode()).isEqualTo( + SliceView.MODE_LARGE); + } + + @Test + public void bindView_shouldSetSlice() { + final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + + assertThat(((SliceViewHolder) viewHolder).sliceView.getSlice().getUri()).isEqualTo( + TEST_SLICE_URI); + } + + private RecyclerView.ViewHolder getSliceViewHolder() { + final RecyclerView recyclerView = new RecyclerView(mActivity); + recyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_FULL_WIDTH, recyclerView, + false); + return mHelper.createViewHolder(view); + } + + private ContextualCard buildContextualCard() { + return new ContextualCard.Builder() + .setName("test_name") + .setCategory(ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE) + .setCardType(ContextualCard.CardType.SLICE) + .setSliceUri(TEST_SLICE_URI) + .setIsHalfWidth(false /* isHalfWidth */) + .build(); + } + + private Slice buildSlice() { + final String title = "test_title"; + final IconCompat icon = IconCompat.createWithResource(mActivity, R.drawable.empty_icon); + final PendingIntent pendingIntent = PendingIntent.getActivity( + mActivity, + title.hashCode() /* requestCode */, + new Intent("test action"), + 0 /* flags */); + final SliceAction action + = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.SMALL_IMAGE, title); + return new ListBuilder(mActivity, TEST_SLICE_URI, ListBuilder.INFINITY) + .addRow(new ListBuilder.RowBuilder() + .addEndItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setPrimaryAction(action)) + .build(); + } +} From 355945078995f7b313e3554328a9f67d2bb626b1 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Thu, 20 Dec 2018 00:04:14 +0800 Subject: [PATCH 5/9] Implement slice half card To display slice in half width form needs to bind slice, extract the title and the icon from slice, and finally put the title and the icon into half card layout. Bug: 119655434 Test: visual, robotests Change-Id: Ib390f9a45f0ee609eb9e3499bff74136c7616ac3 --- .../slices/SliceHalfCardRendererHelper.java | 41 +++++- .../SliceHalfCardRendererHelperTest.java | 119 ++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java index 2986dbc8fe7..24d654d885c 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java @@ -16,13 +16,24 @@ package com.android.settings.homepage.contextualcards.slices; +import android.app.PendingIntent; import android.content.Context; +import android.util.Log; import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import androidx.slice.Slice; +import androidx.slice.SliceMetadata; +import androidx.slice.core.SliceAction; +import androidx.slice.widget.EventInfo; +import com.android.settings.R; import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; +import com.android.settings.overlay.FeatureFactory; /** * Card renderer helper for {@link ContextualCard} built as slice half card. @@ -37,10 +48,38 @@ class SliceHalfCardRendererHelper { } RecyclerView.ViewHolder createViewHolder(View view) { - return null; + return new HalfCardViewHolder(view); } void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice) { + final HalfCardViewHolder view = (HalfCardViewHolder) holder; + final SliceMetadata sliceMetadata = SliceMetadata.from(mContext, slice); + final SliceAction primaryAction = sliceMetadata.getPrimaryAction(); + view.icon.setImageDrawable(primaryAction.getIcon().loadDrawable(mContext)); + view.title.setText(primaryAction.getTitle()); + view.content.setOnClickListener(v -> { + try { + primaryAction.getAction().send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to start intent " + primaryAction.getTitle()); + } + final ContextualCardFeatureProvider contextualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext); + contextualCardFeatureProvider.logContextualCardClick(card, 0 /* row */, + EventInfo.ACTION_TYPE_CONTENT); + }); + } + static class HalfCardViewHolder extends RecyclerView.ViewHolder { + public final LinearLayout content; + public final ImageView icon; + public final TextView title; + + public HalfCardViewHolder(View itemView) { + super(itemView); + content = itemView.findViewById(R.id.content); + icon = itemView.findViewById(android.R.id.icon); + title = itemView.findViewById(android.R.id.title); + } } } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java new file mode 100644 index 00000000000..c38697e16f1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelperTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.homepage.contextualcards.slices; + +import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; +import androidx.slice.widget.SliceLiveData; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; +import com.android.settings.homepage.contextualcards.slices.SliceHalfCardRendererHelper.HalfCardViewHolder; +import com.android.settings.intelligence.ContextualCardProto; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class SliceHalfCardRendererHelperTest { + + private static final Uri TEST_SLICE_URI = Uri.parse("content://test/test"); + + private Activity mActivity; + private SliceHalfCardRendererHelper mHelper; + + @Before + public void setUp() { + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mActivity = Robolectric.buildActivity(Activity.class).create().get(); + mActivity.setTheme(R.style.Theme_Settings_Home); + mHelper = new SliceHalfCardRendererHelper(mActivity); + } + + @Test + public void createViewHolder_shouldAlwaysReturnCustomViewHolder() { + final RecyclerView.ViewHolder viewHolder = getHalfCardViewHolder(); + + assertThat(viewHolder).isInstanceOf(HalfCardViewHolder.class); + } + + @Test + public void bindView_shouldSetTitle() { + final RecyclerView.ViewHolder viewHolder = getHalfCardViewHolder(); + + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice()); + + assertThat(((HalfCardViewHolder) viewHolder).title.getText()).isEqualTo("test_title"); + } + + private RecyclerView.ViewHolder getHalfCardViewHolder() { + final RecyclerView recyclerView = new RecyclerView(mActivity); + recyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_HALF_WIDTH, recyclerView, + false); + + return mHelper.createViewHolder(view); + } + + private ContextualCard buildContextualCard() { + return new ContextualCard.Builder() + .setName("test_name") + .setCategory(ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE) + .setCardType(ContextualCard.CardType.SLICE) + .setSliceUri(TEST_SLICE_URI) + .setIsHalfWidth(false /* isHalfWidth */) + .build(); + } + + private Slice buildSlice() { + final String title = "test_title"; + final IconCompat icon = IconCompat.createWithResource(mActivity, R.drawable.empty_icon); + final PendingIntent pendingIntent = PendingIntent.getActivity( + mActivity, + title.hashCode() /* requestCode */, + new Intent("test action"), + 0 /* flags */); + final SliceAction action + = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.SMALL_IMAGE, title); + return new ListBuilder(mActivity, TEST_SLICE_URI, ListBuilder.INFINITY) + .addRow(new ListBuilder.RowBuilder() + .addEndItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setPrimaryAction(action)) + .build(); + } +} From 5c1d3e96a1b3b1b0f3c4689a55075dcde2a273f4 Mon Sep 17 00:00:00 2001 From: tmfang Date: Mon, 24 Dec 2018 15:09:23 +0800 Subject: [PATCH 6/9] Make filter appear floating in ManagerApplication. Using new layouts to make filter component appear floating on top of the page. The layouts contain the NestedScollView and AppBarLayout. The NestedScrollView was given a layout_behavior which defined the behavior of AppBarLayout. Test: visual Change-Id: Ia76104f80c6b60ac6c8f5937ebface8e9a7238b5 Fixes: 121148001 --- res/layout/manage_applications_apps.xml | 87 ++++++++++++++----------- res/values/dimens.xml | 2 + 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/res/layout/manage_applications_apps.xml b/res/layout/manage_applications_apps.xml index 765ced63652..c2f58c3394b 100644 --- a/res/layout/manage_applications_apps.xml +++ b/res/layout/manage_applications_apps.xml @@ -14,60 +14,73 @@ limitations under the License. --> - - + android:layout_height="match_parent" + android:fillViewport="true" + settings:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior"> - + android:paddingTop="@dimen/app_bar_height"> - + android:layout_height="match_parent" + android:orientation="vertical" + android:visibility="gone"> - - - + android:layout_height="match_parent"> - + - + - + - + - + - + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index b250eff209c..e502f772d6c 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -114,6 +114,8 @@ 182dp 32dp 24dp + + 80dp 88dip From 2ac24f2cebf7d06577396749329159f139132cb4 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Mon, 7 Jan 2019 16:15:08 +0800 Subject: [PATCH 7/9] Refine the conditionals layout 1. Add the minWidth for a button in the condition full tile and adjust the start margin of the button. 2. Decrease the start padding of the conditional header layout. Bug: 113451905 Test: visual Change-Id: I4864730b35154c0bd4b0a806815aa912b19ca3d7 --- res/values/dimens.xml | 2 +- res/values/styles.xml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/res/values/dimens.xml b/res/values/dimens.xml index b250eff209c..b8dbef372f2 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -354,7 +354,7 @@ 12dp 10dp 10dp - 24dp + 16dp 4dp 16dp 16dp diff --git a/res/values/styles.xml b/res/values/styles.xml index c15a3fbb88a..111cdbd5898 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -454,7 +454,8 @@