diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java index e437e2b37f9..32a86e84389 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java @@ -16,8 +16,10 @@ package com.android.settings.homepage.contextualcards; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -25,6 +27,7 @@ import androidx.annotation.VisibleForTesting; import androidx.slice.widget.EventInfo; import com.android.settings.R; +import com.android.settings.intelligence.ContextualCardProto.ContextualCardList; import java.util.List; @@ -38,10 +41,6 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP // Contextual card shows, log card name and rank private static final int CONTEXTUAL_CARD_SHOW = 39; - // Contextual card is eligible to be shown, but doesn't rank high - // enough, log card name and score - private static final int CONTEXTUAL_CARD_NOT_SHOW = 40; - // Contextual card is dismissed, log card name private static final int CONTEXTUAL_CARD_DISMISS = 41; @@ -67,6 +66,11 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP // log type private static final String EXTRA_CONTEXTUALCARD_ACTION_TYPE = "type"; + // displayed contextual cards + private static final String EXTRA_CONTEXTUALCARD_VISIBLE = "visible"; + + // hidden contextual cards + private static final String EXTRA_CONTEXTUALCARD_HIDDEN = "hidden"; // Contextual card tap target private static final int TARGET_DEFAULT = 0; @@ -82,6 +86,10 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP @Override public void logHomepageDisplay(Context context, Long latency) { + final Intent intent = new Intent(); + intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_HOME_SHOW); + intent.putExtra(EXTRA_LATENCY, latency); + sendBroadcast(context, intent); } @Override @@ -94,8 +102,13 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP } @Override - public void logContextualCardDisplay(Context context, List showCards, + public void logContextualCardDisplay(Context context, List visibleCards, List hiddenCards) { + final Intent intent = new Intent(); + intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_CARD_SHOW); + intent.putExtra(EXTRA_CONTEXTUALCARD_VISIBLE, serialize(visibleCards)); + intent.putExtra(EXTRA_CONTEXTUALCARD_HIDDEN, serialize(hiddenCards)); + sendBroadcast(context, intent); } @Override @@ -116,7 +129,7 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP final String action = context.getString(R.string.config_settingsintelligence_log_action); if (!TextUtils.isEmpty(action)) { intent.setAction(action); - context.sendBroadcast(intent); + context.sendBroadcastAsUser(intent, UserHandle.ALL); } } @@ -133,4 +146,16 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP return TARGET_DEFAULT; } } + + @VisibleForTesting + @NonNull + byte[] serialize(List cards) { + final ContextualCardList.Builder builder = ContextualCardList.newBuilder(); + cards.stream().forEach(card -> builder.addCard( + com.android.settings.intelligence.ContextualCardProto.ContextualCard.newBuilder() + .setSliceUri(card.getSliceUri().toString()) + .setCardName(card.getName()) + .build())); + return builder.build().toByteArray(); + } } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java index 3ef465333fd..6373519c770 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java @@ -20,6 +20,9 @@ import static android.app.slice.Slice.HINT_ERROR; import static androidx.slice.widget.SliceLiveData.SUPPORTED_SPECS; +import static com.android.settings.slices.CustomSliceRegistry.CONNECTED_DEVICE_SLICE_URI; +import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI; + import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; @@ -34,7 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.slice.Slice; -import com.android.settings.slices.CustomSliceRegistry; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.utils.AsyncLoaderCompat; import java.util.ArrayList; @@ -103,27 +106,50 @@ public class ContextualCardLoader extends AsyncLoaderCompat return getFinalDisplayableCards(result); } + // Get final displayed cards and log what cards will be displayed/hidden @VisibleForTesting List getFinalDisplayableCards(List candidates) { - List eligibleCards = filterEligibleCards(candidates); - eligibleCards = eligibleCards.stream().limit(DEFAULT_CARD_COUNT).collect( - Collectors.toList()); + final List eligibleCards = filterEligibleCards(candidates); + final List visibleCards = new ArrayList<>(); + final List hiddenCards = new ArrayList<>(); - if (eligibleCards.size() <= 2 || getNumberOfLargeCard(eligibleCards) == 0) { - return eligibleCards; + final int size = eligibleCards.size(); + for (int i = 0; i < size; i++) { + if (i < DEFAULT_CARD_COUNT) { + visibleCards.add(eligibleCards.get(i)); + } else { + hiddenCards.add(eligibleCards.get(i)); + } } - if (eligibleCards.size() == DEFAULT_CARD_COUNT) { - eligibleCards.remove(eligibleCards.size() - 1); + try { + // The maximum cards are four small cards OR + // one large card with two small cards OR + // two large cards + if (visibleCards.size() <= 2 || getNumberOfLargeCard(visibleCards) == 0) { + // four small cards + return visibleCards; + } + + if (visibleCards.size() == DEFAULT_CARD_COUNT) { + hiddenCards.add(visibleCards.remove(visibleCards.size() - 1)); + } + + if (getNumberOfLargeCard(visibleCards) == 1) { + // One large card with two small cards + return visibleCards; + } + + hiddenCards.add(visibleCards.remove(visibleCards.size() - 1)); + + // Two large cards + return visibleCards; + } finally { + final ContextualCardFeatureProvider contextualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(); + contextualCardFeatureProvider.logContextualCardDisplay(mContext, visibleCards, + hiddenCards); } - - if (getNumberOfLargeCard(eligibleCards) == 1) { - return eligibleCards; - } - - eligibleCards.remove(eligibleCards.size() - 1); - - return eligibleCards; } @VisibleForTesting @@ -169,8 +195,8 @@ public class ContextualCardLoader extends AsyncLoaderCompat private int getNumberOfLargeCard(List cards) { return (int) cards.stream() - .filter(card -> card.getSliceUri().equals(CustomSliceRegistry.WIFI_SLICE_URI) - || card.getSliceUri().equals(CustomSliceRegistry.CONNECTED_DEVICE_SLICE_URI)) + .filter(card -> card.getSliceUri().equals(WIFI_SLICE_URI) + || card.getSliceUri().equals(CONNECTED_DEVICE_SLICE_URI)) .count(); } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java index 5f397d787eb..28ad0d30e8e 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java @@ -16,8 +16,7 @@ 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 java.util.stream.Collectors.groupingBy; @@ -33,6 +32,7 @@ import androidx.annotation.VisibleForTesting; import androidx.loader.app.LoaderManager; 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; @@ -72,6 +72,7 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo private final List mLifecycleObservers; private ContextualCardUpdateListener mListener; + private long mStartTime; public ContextualCardManager(Context context, Lifecycle lifecycle) { mContext = context; @@ -86,6 +87,7 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo } void loadContextualCards(ContextualCardsFragment fragment) { + mStartTime = System.currentTimeMillis(); final CardContentLoaderCallbacks cardContentLoaderCallbacks = new CardContentLoaderCallbacks(mContext); cardContentLoaderCallbacks.setListener(this); @@ -168,6 +170,10 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo @Override public void onFinishCardLoading(List cards) { onContextualCardUpdated(cards.stream().collect(groupingBy(ContextualCard::getCardType))); + final long elapsedTime = System.currentTimeMillis() - mStartTime; + final ContextualCardFeatureProvider contextualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(); + contextualCardFeatureProvider.logHomepageDisplay(mContext, elapsedTime); } public ControllerRendererPool getControllerRendererPool() { diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java index 08631f7bdfc..b3f94114776 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java @@ -16,13 +16,20 @@ package com.android.settings.homepage.contextualcards; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; +import android.net.Uri; +import android.os.UserHandle; +import com.android.settings.intelligence.ContextualCardProto.ContextualCardList; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; @@ -31,6 +38,9 @@ import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.ArrayList; +import java.util.List; + @RunWith(SettingsRobolectricTestRunner.class) public class ContextualCardFeatureProviderImplTest { @@ -48,7 +58,7 @@ public class ContextualCardFeatureProviderImplTest { final Intent intent = new Intent(); mImpl.sendBroadcast(mContext, intent); - verify(mContext, never()).sendBroadcast(intent); + verify(mContext, never()).sendBroadcastAsUser(intent, UserHandle.ALL); } @Test @@ -57,6 +67,37 @@ public class ContextualCardFeatureProviderImplTest { final Intent intent = new Intent(); mImpl.sendBroadcast(mContext, intent); - verify(mContext).sendBroadcast(intent); + verify(mContext).sendBroadcastAsUser(intent, UserHandle.ALL); + } + + @Test + @Config(qualifiers = "mcc999") + public void logContextualCardDisplay_hasAction_sendBroadcast() { + mImpl.logContextualCardDisplay(mContext, new ArrayList<>(), new ArrayList<>()); + + verify(mContext).sendBroadcastAsUser(any(Intent.class), any()); + } + + @Test + public void serialize_hasSizeTwo_returnSizeTwo() { + final List cards = new ArrayList<>(); + cards.add(new ContextualCard.Builder() + .setName("name1") + .setSliceUri(Uri.parse("uri1")) + .build()); + cards.add(new ContextualCard.Builder() + .setName("name2") + .setSliceUri(Uri.parse("uri2")) + .build()); + + + final byte[] data = mImpl.serialize(cards); + + try { + assertThat(ContextualCardList + .parseFrom(data).getCardCount()).isEqualTo(cards.size()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } } } \ No newline at end of file