Don't have cards auto-filled in the foreground when a card gets dismissed.

Currently if users dismiss a card or navigate back to the homepage,
new suggestions will be auto filled in. We should only add new cards
upon next visit.

Also fix suggestion cards undismissable problem.

Fixes: 121175037
Bug: 120628661
Test: robotests
Change-Id: I01d74aaaa21c8408e5cecafef04a7d52c97bccc5
This commit is contained in:
Yi-Ling Chuang
2018-12-07 20:13:56 +08:00
parent 4eddb1b1a4
commit 39dcad9bd1
3 changed files with 128 additions and 7 deletions

View File

@@ -35,6 +35,7 @@ import androidx.loader.content.Loader;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import java.util.ArrayList;
import java.util.List;
@@ -56,7 +57,9 @@ import java.util.stream.Collectors;
* get the page refreshed.
*/
public class ContextualCardManager implements ContextualCardLoader.CardContentLoaderListener,
ContextualCardUpdateListener {
ContextualCardUpdateListener, LifecycleObserver, OnSaveInstanceState {
private static final String KEY_CONTEXTUAL_CARDS = "key_contextual_cards";
private static final String TAG = "ContextualCardManager";
@@ -68,6 +71,9 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
final List<ContextualCard> mContextualCards;
@VisibleForTesting
long mStartTime;
boolean mIsFirstLaunch;
@VisibleForTesting
List<String> mSavedCards;
private final Context mContext;
private final ControllerRendererPool mControllerRendererPool;
@@ -76,12 +82,20 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
private ContextualCardUpdateListener mListener;
public ContextualCardManager(Context context, Lifecycle lifecycle) {
public ContextualCardManager(Context context, Lifecycle lifecycle, Bundle savedInstanceState) {
mContext = context;
mLifecycle = lifecycle;
mContextualCards = new ArrayList<>();
mLifecycleObservers = new ArrayList<>();
mControllerRendererPool = new ControllerRendererPool();
mLifecycle.addObserver(this);
if (savedInstanceState == null) {
mIsFirstLaunch = true;
mSavedCards = null;
} else {
mSavedCards = savedInstanceState.getStringArrayList(KEY_CONTEXTUAL_CARDS);
}
//for data provided by Settings
for (@ContextualCard.CardType int cardType : SETTINGS_CARDS) {
setupController(cardType);
@@ -172,13 +186,34 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
@Override
public void onFinishCardLoading(List<ContextualCard> cards) {
final long loadTime = System.currentTimeMillis() - mStartTime;
final List<ContextualCard> cardsToKeep = getCardsToKeep(cards);
//navigate back to the homepage or after card dismissal
if (!mIsFirstLaunch) {
onContextualCardUpdated(cardsToKeep.stream()
.collect(groupingBy(ContextualCard::getCardType)));
return;
}
//only log homepage display upon a fresh launch
if (loadTime <= ContextualCardLoader.CARD_CONTENT_LOADER_TIMEOUT_MS) {
onContextualCardUpdated(
cards.stream().collect(groupingBy(ContextualCard::getCardType)));
onContextualCardUpdated(cards.stream()
.collect(groupingBy(ContextualCard::getCardType)));
}
final long totalTime = System.currentTimeMillis() - mStartTime;
FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider()
.logHomepageDisplay(mContext, totalTime);
mIsFirstLaunch = false;
}
@Override
public void onSaveInstanceState(Bundle outState) {
final ArrayList<String> cards = mContextualCards.stream()
.map(ContextualCard::getName)
.collect(Collectors.toCollection(ArrayList::new));
outState.putStringArrayList(KEY_CONTEXTUAL_CARDS, cards);
}
public ControllerRendererPool getControllerRendererPool() {
@@ -189,6 +224,22 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
mListener = listener;
}
private List<ContextualCard> getCardsToKeep(List<ContextualCard> cards) {
if (mSavedCards != null) {
//screen rotate
final List<ContextualCard> cardsToKeep = cards.stream()
.filter(card -> mSavedCards.contains(card.getName()))
.collect(Collectors.toList());
mSavedCards = null;
return cardsToKeep;
} else {
//navigate back to the homepage or after dismissing a card
return cards.stream()
.filter(card -> mContextualCards.contains(card))
.collect(Collectors.toList());
}
}
static class CardContentLoaderCallbacks implements
LoaderManager.LoaderCallbacks<List<ContextualCard>> {

View File

@@ -42,7 +42,8 @@ public class ContextualCardsFragment extends InstrumentedFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContextualCardManager = new ContextualCardManager(getContext(), getSettingsLifecycle());
mContextualCardManager = new ContextualCardManager(getContext(), getSettingsLifecycle(),
savedInstanceState);
}
@Override

View File

@@ -42,13 +42,16 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RunWith(RobolectricTestRunner.class)
public class ContextualCardManagerTest {
private static final String TEST_SLICE_URI = "context://test/test";
private static final String TEST_SLICE_NAME = "test_name";
@Mock
ContextualCardUpdateListener mListener;
@@ -61,7 +64,8 @@ public class ContextualCardManagerTest {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
final ContextualCardsFragment fragment = new ContextualCardsFragment();
mManager = new ContextualCardManager(mContext, fragment.getSettingsLifecycle());
mManager = new ContextualCardManager(mContext, fragment.getSettingsLifecycle(),
null /* bundle */);
}
@Test
@@ -135,9 +139,74 @@ public class ContextualCardManagerTest {
verify(manager, never()).onContextualCardUpdated(anyMap());
}
@Test
public void onFinishCardLoading_newLaunch_twoLoadedCards_shouldShowTwoCards() {
mManager.mStartTime = System.currentTimeMillis();
mManager.setListener(mListener);
final List<ContextualCard> cards = new ArrayList<>();
cards.add(buildContextualCard(TEST_SLICE_URI));
cards.add(buildContextualCard(TEST_SLICE_URI));
mManager.onFinishCardLoading(cards);
assertThat(mManager.mContextualCards).hasSize(2);
}
@Test
public void onFinishCardLoading_hasSavedCard_shouldOnlyShowSavedCard() {
mManager.setListener(mListener);
final List<String> savedCardNames = new ArrayList<>();
savedCardNames.add(TEST_SLICE_NAME);
mManager.mIsFirstLaunch = false;
mManager.mSavedCards = savedCardNames;
final ContextualCard newCard =
new ContextualCard.Builder()
.setName("test_name2")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(Uri.parse("content://test/test2"))
.build();
final List<ContextualCard> loadedCards = new ArrayList<>();
loadedCards.add(buildContextualCard(TEST_SLICE_URI));
loadedCards.add(newCard);
mManager.onFinishCardLoading(loadedCards);
final List<String> actualCards = mManager.mContextualCards.stream()
.map(ContextualCard::getName)
.collect(Collectors.toList());
final List<String> expectedCards = Arrays.asList(TEST_SLICE_NAME);
assertThat(actualCards).containsExactlyElementsIn(expectedCards);
}
@Test
public void onFinishCardLoading_reloadData_shouldOnlyShowOldCard() {
mManager.setListener(mListener);
mManager.mIsFirstLaunch = false;
//old card
mManager.mContextualCards.add(buildContextualCard(TEST_SLICE_URI));
final ContextualCard newCard =
new ContextualCard.Builder()
.setName("test_name2")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(Uri.parse("content://test/test2"))
.build();
final List<ContextualCard> loadedCards = new ArrayList<>();
loadedCards.add(buildContextualCard(TEST_SLICE_URI));
loadedCards.add(newCard);
mManager.onFinishCardLoading(loadedCards);
final List<String> actualCards = mManager.mContextualCards.stream()
.map(ContextualCard::getName)
.collect(Collectors.toList());
final List<String> expectedCards = Arrays.asList(TEST_SLICE_NAME);
assertThat(actualCards).containsExactlyElementsIn(expectedCards);
}
private ContextualCard buildContextualCard(String sliceUri) {
return new ContextualCard.Builder()
.setName("test_name")
.setName(TEST_SLICE_NAME)
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(Uri.parse(sliceUri))
.build();
}