Add log feature

- Add ContextualCardFeatureProvider to handle card interaction

Bug: 79698338
Test: manual
Change-Id: I2a76f5ccfd07072a98ee927bed7dc39731d4cb09
This commit is contained in:
Raff Tsai
2018-11-16 15:06:46 +08:00
parent d4fc724f95
commit da7a5d1171
11 changed files with 301 additions and 4 deletions

View File

@@ -151,6 +151,9 @@
com.android.settings.intelligence com.android.settings.intelligence
</string> </string>
<!-- Settings intelligence interaction log intent action -->
<string name="config_settingsintelligence_log_action" translatable="false"></string>
<!-- Emergency app package name --> <!-- Emergency app package name -->
<string name="config_emergency_package_name" translatable="false"> <string name="config_emergency_package_name" translatable="false">
com.android.emergency com.android.emergency

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2018 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;
import android.content.Context;
import java.util.List;
/** Feature provider for the contextual card feature. */
public interface ContextualCardFeatureProvider {
/** Homepage displays. */
public void logHomepageDisplay(Context context, Long latency);
/** When user clicks dismiss in contextual card */
public void logContextualCardDismiss(Context context, ContextualCard card);
/** After ContextualCardManager decides which cards will be displayed/hidden */
public void logContextualCardDisplay(Context context, List<ContextualCard> showedCards,
List<ContextualCard> hiddenCards);
/** When user clicks toggle/title area of a contextual card. */
public void logContextualCardClick(Context context, ContextualCard card, int row,
int tapTarget);
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2018 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;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.slice.widget.EventInfo;
import com.android.settings.R;
import java.util.List;
public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureProvider {
private static final String TAG = "ContextualCardFeature";
// Contextual card interaction logs
// Settings Homepage shows
private static final int CONTEXTUAL_HOME_SHOW = 38;
// 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;
// Contextual card is clicked , log card name, score, tap area
private static final int CONTEXTUAL_CARD_CLICK = 42;
// SettingsLogBroadcastReceiver contracts
// contextual card name
private static final String EXTRA_CONTEXTUALCARD_NAME = "name";
// contextual card score
private static final String EXTRA_CONTEXTUALCARD_SCORE = "score";
// contextual card clicked row
private static final String EXTRA_CONTEXTUALCARD_ROW = "row";
// contextual card tap target
private static final String EXTRA_CONTEXTUALCARD_TAP_TARGET = "target";
// contextual homepage display latency
private static final String EXTRA_LATENCY = "latency";
// log type
private static final String EXTRA_CONTEXTUALCARD_ACTION_TYPE = "type";
// Contextual card tap target
private static final int TARGET_DEFAULT = 0;
// Click title area
private static final int TARGET_TITLE = 1;
// Click toggle
private static final int TARGET_TOGGLE = 2;
// Click slider
private static final int TARGET_SLIDER = 3;
@Override
public void logHomepageDisplay(Context context, Long latency) {
}
@Override
public void logContextualCardDismiss(Context context, ContextualCard card) {
final Intent intent = new Intent();
intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_CARD_DISMISS);
intent.putExtra(EXTRA_CONTEXTUALCARD_NAME, card.getName());
intent.putExtra(EXTRA_CONTEXTUALCARD_SCORE, card.getRankingScore());
sendBroadcast(context, intent);
}
@Override
public void logContextualCardDisplay(Context context, List<ContextualCard> showCards,
List<ContextualCard> hiddenCards) {
}
@Override
public void logContextualCardClick(Context context, ContextualCard card, int row,
int actionType) {
final Intent intent = new Intent();
intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_CARD_CLICK);
intent.putExtra(EXTRA_CONTEXTUALCARD_NAME, card.getName());
intent.putExtra(EXTRA_CONTEXTUALCARD_SCORE, card.getRankingScore());
intent.putExtra(EXTRA_CONTEXTUALCARD_ROW, row);
intent.putExtra(EXTRA_CONTEXTUALCARD_TAP_TARGET, actionTypeToTapTarget(actionType));
sendBroadcast(context, intent);
}
@VisibleForTesting
void sendBroadcast(final Context context, final Intent intent) {
intent.setPackage(context.getString(R.string.config_settingsintelligence_package_name));
final String action = context.getString(R.string.config_settingsintelligence_log_action);
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
context.sendBroadcast(intent);
}
}
private int actionTypeToTapTarget(int actionType) {
switch (actionType) {
case EventInfo.ACTION_TYPE_CONTENT:
return TARGET_TITLE;
case EventInfo.ACTION_TYPE_TOGGLE:
return TARGET_TOGGLE;
case EventInfo.ACTION_TYPE_SLIDER:
return TARGET_SLIDER;
default:
Log.w(TAG, "unknown type " + actionType);
return TARGET_DEFAULT;
}
}
}

View File

@@ -26,8 +26,10 @@ import com.android.settings.R;
import com.android.settings.homepage.contextualcards.CardDatabaseHelper; import com.android.settings.homepage.contextualcards.CardDatabaseHelper;
import com.android.settings.homepage.contextualcards.ContextualCard; import com.android.settings.homepage.contextualcards.ContextualCard;
import com.android.settings.homepage.contextualcards.ContextualCardController; import com.android.settings.homepage.contextualcards.ContextualCardController;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog; import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog;
import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener; import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
/** /**
@@ -67,6 +69,9 @@ public class SliceContextualCardController implements ContextualCardController {
dbHelper.markContextualCardAsDismissed(mContext, card.getName()); dbHelper.markContextualCardAsDismissed(mContext, card.getName());
}); });
showFeedbackDialog(card); showFeedbackDialog(card);
final ContextualCardFeatureProvider contexualCardFeatureProvider =
FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider();
contexualCardFeatureProvider.logContextualCardDismiss(mContext, card);
} }
@Override @Override

View File

@@ -20,6 +20,7 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@@ -38,10 +39,13 @@ import androidx.slice.widget.SliceView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.homepage.contextualcards.ContextualCard; 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.ContextualCardRenderer;
import com.android.settings.homepage.contextualcards.ControllerRendererPool; import com.android.settings.homepage.contextualcards.ControllerRendererPool;
import com.android.settings.overlay.FeatureFactory;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* Card renderer for {@link ContextualCard} built as slices. * Card renderer for {@link ContextualCard} built as slices.
@@ -58,6 +62,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
private final Context mContext; private final Context mContext;
private final LifecycleOwner mLifecycleOwner; private final LifecycleOwner mLifecycleOwner;
private final ControllerRendererPool mControllerRendererPool; private final ControllerRendererPool mControllerRendererPool;
private final Set<ContextualCard> mCardSet;
public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner, public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner,
ControllerRendererPool controllerRendererPool) { ControllerRendererPool controllerRendererPool) {
@@ -65,6 +70,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
mLifecycleOwner = lifecycleOwner; mLifecycleOwner = lifecycleOwner;
mSliceLiveDataMap = new ArrayMap<>(); mSliceLiveDataMap = new ArrayMap<>();
mControllerRendererPool = controllerRendererPool; mControllerRendererPool = controllerRendererPool;
mCardSet = new ArraySet<>();
} }
@Override @Override
@@ -99,6 +105,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
sliceLiveData = SliceLiveData.fromUri(mContext, uri); sliceLiveData = SliceLiveData.fromUri(mContext, uri);
mSliceLiveDataMap.put(uri.toString(), sliceLiveData); mSliceLiveDataMap.put(uri.toString(), sliceLiveData);
} }
mCardSet.add(card);
sliceLiveData.removeObservers(mLifecycleOwner); sliceLiveData.removeObservers(mLifecycleOwner);
sliceLiveData.observe(mLifecycleOwner, slice -> { sliceLiveData.observe(mLifecycleOwner, slice -> {
@@ -128,14 +135,27 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove); final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove);
btnRemove.setOnClickListener(v -> { btnRemove.setOnClickListener(v -> {
mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed( mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card);
card);
}); });
} }
@Override @Override
public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
//TODO(b/79698338): Log user interaction //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();
contexualCardFeatureProvider.logContextualCardClick(mContext, card,
eventInfo.rowIndex, eventInfo.actionType);
break;
}
}
} }
public static class SliceViewHolder extends RecyclerView.ViewHolder { public static class SliceViewHolder extends RecyclerView.ViewHolder {

View File

@@ -28,6 +28,7 @@ import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.gestures.AssistGestureFeatureProvider; import com.android.settings.gestures.AssistGestureFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.localepicker.LocaleFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider;
import com.android.settings.panel.PanelFeatureProvider; import com.android.settings.panel.PanelFeatureProvider;
import com.android.settings.search.SearchFeatureProvider; import com.android.settings.search.SearchFeatureProvider;
@@ -108,6 +109,8 @@ public abstract class FeatureFactory {
public abstract PanelFeatureProvider getPanelFeatureProvider(); public abstract PanelFeatureProvider getPanelFeatureProvider();
public abstract ContextualCardFeatureProvider getContextualCardFeatureProvider();
public static final class FactoryNotFoundException extends RuntimeException { public static final class FactoryNotFoundException extends RuntimeException {
public FactoryNotFoundException(Throwable throwable) { public FactoryNotFoundException(Throwable throwable) {
super("Unable to create factory. Did you misconfigure Proguard?", throwable); super("Unable to create factory. Did you misconfigure Proguard?", throwable);

View File

@@ -40,6 +40,8 @@ import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.fuelgauge.PowerUsageFeatureProviderImpl; import com.android.settings.fuelgauge.PowerUsageFeatureProviderImpl;
import com.android.settings.gestures.AssistGestureFeatureProvider; import com.android.settings.gestures.AssistGestureFeatureProvider;
import com.android.settings.gestures.AssistGestureFeatureProviderImpl; import com.android.settings.gestures.AssistGestureFeatureProviderImpl;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProviderImpl;
import com.android.settings.localepicker.LocaleFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider;
import com.android.settings.localepicker.LocaleFeatureProviderImpl; import com.android.settings.localepicker.LocaleFeatureProviderImpl;
import com.android.settings.panel.PanelFeatureProvider; import com.android.settings.panel.PanelFeatureProvider;
@@ -75,6 +77,7 @@ public class FeatureFactoryImpl extends FeatureFactory {
private SlicesFeatureProvider mSlicesFeatureProvider; private SlicesFeatureProvider mSlicesFeatureProvider;
private AccountFeatureProvider mAccountFeatureProvider; private AccountFeatureProvider mAccountFeatureProvider;
private PanelFeatureProvider mPanelFeatureProvider; private PanelFeatureProvider mPanelFeatureProvider;
private ContextualCardFeatureProvider mContextualCardFeatureProvider;
@Override @Override
public SupportFeatureProvider getSupportFeatureProvider(Context context) { public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -220,4 +223,11 @@ public class FeatureFactoryImpl extends FeatureFactory {
} }
return mPanelFeatureProvider; return mPanelFeatureProvider;
} }
public ContextualCardFeatureProvider getContextualCardFeatureProvider() {
if (mContextualCardFeatureProvider == null) {
mContextualCardFeatureProvider = new ContextualCardFeatureProviderImpl();
}
return mContextualCardFeatureProvider;
}
} }

View File

@@ -73,6 +73,11 @@
<item>fake_package/fake_service</item> <item>fake_package/fake_service</item>
</string-array> </string-array>
<!-- Settings intelligence interaction log intent action -->
<string name="config_settingsintelligence_log_action" translatable="false">
aaa.bbb.ccc
</string>
<!-- List of packages that should be whitelisted for slice uri access. Do not translate --> <!-- List of packages that should be whitelisted for slice uri access. Do not translate -->
<string-array name="slice_whitelist_package_names" translatable="false"> <string-array name="slice_whitelist_package_names" translatable="false">
<item>com.android.settings.slice_whitelist_package</item> <item>com.android.settings.slice_whitelist_package</item>

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2018 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;
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 com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
public class ContextualCardFeatureProviderImplTest {
private Context mContext;
private ContextualCardFeatureProviderImpl mImpl;
@Before
public void setUp() {
mContext = spy(RuntimeEnvironment.application);
mImpl = new ContextualCardFeatureProviderImpl();
}
@Test
public void sendBroadcast_emptyAction_notSendBroadcast() {
final Intent intent = new Intent();
mImpl.sendBroadcast(mContext, intent);
verify(mContext, never()).sendBroadcast(intent);
}
@Test
@Config(qualifiers = "mcc999")
public void sendBroadcast_hasAction_sendBroadcast() {
final Intent intent = new Intent();
mImpl.sendBroadcast(mContext, intent);
verify(mContext).sendBroadcast(intent);
}
}

View File

@@ -18,9 +18,10 @@ package com.android.settings.homepage.contextualcards.slices;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
@@ -33,6 +34,7 @@ import com.android.settings.homepage.contextualcards.CardDatabaseHelper;
import com.android.settings.homepage.contextualcards.ContextualCard; import com.android.settings.homepage.contextualcards.ContextualCard;
import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog; import com.android.settings.homepage.contextualcards.ContextualCardFeedbackDialog;
import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before; import org.junit.Before;
@@ -57,6 +59,7 @@ public class SliceContextualCardControllerTest {
private CardContentProvider mProvider; private CardContentProvider mProvider;
private ContentResolver mResolver; private ContentResolver mResolver;
private SliceContextualCardController mController; private SliceContextualCardController mController;
private FakeFeatureFactory mFeatureFactory;
@Before @Before
public void setUp() { public void setUp() {
@@ -67,6 +70,7 @@ public class SliceContextualCardControllerTest {
mProvider); mProvider);
mResolver = mContext.getContentResolver(); mResolver = mContext.getContentResolver();
mController = spy(new SliceContextualCardController(mContext)); mController = spy(new SliceContextualCardController(mContext));
mFeatureFactory = FakeFeatureFactory.setupForTest();
} }
@Test @Test
@@ -75,7 +79,8 @@ public class SliceContextualCardControllerTest {
mResolver.insert(providerUri, generateOneRow()); mResolver.insert(providerUri, generateOneRow());
doNothing().when(mController).showFeedbackDialog(any(ContextualCard.class)); doNothing().when(mController).showFeedbackDialog(any(ContextualCard.class));
mController.onDismissed(getTestSliceCard()); final ContextualCard card = getTestSliceCard();
mController.onDismissed(card);
final String[] columns = {CardDatabaseHelper.CardColumns.CARD_DISMISSED}; final String[] columns = {CardDatabaseHelper.CardColumns.CARD_DISMISSED};
final String selection = CardDatabaseHelper.CardColumns.NAME + "=?"; final String selection = CardDatabaseHelper.CardColumns.NAME + "=?";
@@ -86,6 +91,8 @@ public class SliceContextualCardControllerTest {
cr.close(); cr.close();
assertThat(qryDismissed).isEqualTo(1); assertThat(qryDismissed).isEqualTo(1);
verify(mFeatureFactory.mContextualCardFeatureProvider).logContextualCardDismiss(
mContext, card);
} }
@Test @Test

View File

@@ -28,6 +28,7 @@ import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.gestures.AssistGestureFeatureProvider; import com.android.settings.gestures.AssistGestureFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
import com.android.settings.localepicker.LocaleFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider;
import com.android.settings.overlay.DockUpdaterFeatureProvider; import com.android.settings.overlay.DockUpdaterFeatureProvider;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
@@ -63,6 +64,7 @@ public class FakeFeatureFactory extends FeatureFactory {
public final AssistGestureFeatureProvider assistGestureFeatureProvider; public final AssistGestureFeatureProvider assistGestureFeatureProvider;
public final AccountFeatureProvider mAccountFeatureProvider; public final AccountFeatureProvider mAccountFeatureProvider;
public final PanelFeatureProvider mPanelFeatureProvider; public final PanelFeatureProvider mPanelFeatureProvider;
public final ContextualCardFeatureProvider mContextualCardFeatureProvider;
public SlicesFeatureProvider slicesFeatureProvider; public SlicesFeatureProvider slicesFeatureProvider;
public SearchFeatureProvider searchFeatureProvider; public SearchFeatureProvider searchFeatureProvider;
@@ -105,6 +107,7 @@ public class FakeFeatureFactory extends FeatureFactory {
slicesFeatureProvider = mock(SlicesFeatureProvider.class); slicesFeatureProvider = mock(SlicesFeatureProvider.class);
mAccountFeatureProvider = mock(AccountFeatureProvider.class); mAccountFeatureProvider = mock(AccountFeatureProvider.class);
mPanelFeatureProvider = mock(PanelFeatureProvider.class); mPanelFeatureProvider = mock(PanelFeatureProvider.class);
mContextualCardFeatureProvider = mock(ContextualCardFeatureProvider.class);
} }
@Override @Override
@@ -191,4 +194,8 @@ public class FakeFeatureFactory extends FeatureFactory {
public PanelFeatureProvider getPanelFeatureProvider() { public PanelFeatureProvider getPanelFeatureProvider() {
return mPanelFeatureProvider; return mPanelFeatureProvider;
} }
public ContextualCardFeatureProvider getContextualCardFeatureProvider() {
return mContextualCardFeatureProvider;
}
} }