Merge "Make filter appear floating in ManagerApplication."
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index 88478e3..49e2a76 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -107,12 +107,12 @@
}
}
}
- return getFinalDisplayableCards(result);
+ return getDisplayableCards(result);
}
// Get final displayed cards and log what cards will be displayed/hidden
@VisibleForTesting
- List<ContextualCard> getFinalDisplayableCards(List<ContextualCard> candidates) {
+ List<ContextualCard> getDisplayableCards(List<ContextualCard> candidates) {
final List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
final List<ContextualCard> visibleCards = new ArrayList<>();
final List<ContextualCard> hiddenCards = new ArrayList<>();
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index 067de7c..12088f8 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 @@
//replace with the new data
mContextualCards.clear();
- mContextualCards.addAll(sortCards(allCards));
+ final List<ContextualCard> sortedCards = sortCards(allCards);
+ mContextualCards.addAll(assignCardWidth(sortedCards));
loadCardControllers();
@@ -224,6 +226,24 @@
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) {
if (mSavedCards != null) {
//screen rotate
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java
index 2986dbc..24d654d 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 @@
}
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/ContextualCardLoaderTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardLoaderTest.java
index c62a6bb..c47fa38 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 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 @@
}
@Test
- public void getFinalDisplayableCards_twoEligibleCards_shouldShowAll() {
+ public void getDisplayableCards_twoEligibleCards_shouldShowAll() {
final List<ContextualCard> cards = getContextualCardList().stream().limit(2)
.collect(Collectors.toList());
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());
}
@Test
- public void getFinalDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() {
+ public void getDisplayableCards_fiveEligibleCardsNoLarge_shouldShowDefaultCardCount() {
final List<ContextualCard> fiveCards = getContextualCardListWithNoLargeCard();
doReturn(fiveCards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
- final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(
+ final List<ContextualCard> result = mContextualCardLoader.getDisplayableCards(
fiveCards);
assertThat(result).hasSize(DEFAULT_CARD_COUNT);
}
@Test
- public void getFinalDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() {
+ public void getDisplayableCards_threeEligibleCardsOneLarge_shouldShowThreeCards() {
final List<ContextualCard> cards = getContextualCardList().stream().limit(2)
.collect(Collectors.toList());
cards.add(new ContextualCard.Builder()
@@ -115,18 +115,18 @@
.build());
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);
}
@Test
- public void getFinalDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() {
+ public void getDisplayableCards_threeEligibleCardsTwoLarge_shouldShowTwoCards() {
final List<ContextualCard> threeCards = getContextualCardList().stream().limit(3)
.collect(Collectors.toList());
doReturn(threeCards).when(mContextualCardLoader).filterEligibleCards(any(List.class));
- final List<ContextualCard> result = mContextualCardLoader.getFinalDisplayableCards(
+ final List<ContextualCard> 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 c405ffc..3fc8e06 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 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 @@
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) {
return new ContextualCard.Builder()
.setName(TEST_SLICE_NAME)
@@ -210,4 +340,45 @@
.setSliceUri(Uri.parse(sliceUri))
.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;
+ }
}
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 0000000..c38697e
--- /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();
+ }
+}