diff --git a/res/values/config.xml b/res/values/config.xml index 6b0a10e29fc..edd948f7962 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -151,6 +151,9 @@ com.android.settings.intelligence + + + com.android.emergency diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java new file mode 100644 index 00000000000..8583f38c78b --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java @@ -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 showedCards, + List hiddenCards); + + /** When user clicks toggle/title area of a contextual card. */ + public void logContextualCardClick(Context context, ContextualCard card, int row, + int tapTarget); +} diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java new file mode 100644 index 00000000000..e437e2b37f9 --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java @@ -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 showCards, + List 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; + } + } +} diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java index 3368580e002..4378be3f0d7 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java @@ -26,8 +26,10 @@ import com.android.settings.R; import com.android.settings.homepage.contextualcards.CardDatabaseHelper; import com.android.settings.homepage.contextualcards.ContextualCard; 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.ContextualCardUpdateListener; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.utils.ThreadUtils; /** @@ -67,6 +69,9 @@ public class SliceContextualCardController implements ContextualCardController { dbHelper.markContextualCardAsDismissed(mContext, card.getName()); }); showFeedbackDialog(card); + final ContextualCardFeatureProvider contexualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(); + contexualCardFeatureProvider.logContextualCardDismiss(mContext, card); } @Override diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java index 74f25eb7478..a2d6e2b119c 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java @@ -20,6 +20,7 @@ import android.content.ContentResolver; import android.content.Context; import android.net.Uri; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.view.View; import android.widget.Button; @@ -38,10 +39,13 @@ 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.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. @@ -58,6 +62,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, private final Context mContext; private final LifecycleOwner mLifecycleOwner; private final ControllerRendererPool mControllerRendererPool; + private final Set mCardSet; public SliceContextualCardRenderer(Context context, LifecycleOwner lifecycleOwner, ControllerRendererPool controllerRendererPool) { @@ -65,6 +70,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, mLifecycleOwner = lifecycleOwner; mSliceLiveDataMap = new ArrayMap<>(); mControllerRendererPool = controllerRendererPool; + mCardSet = new ArraySet<>(); } @Override @@ -99,6 +105,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, sliceLiveData = SliceLiveData.fromUri(mContext, uri); mSliceLiveDataMap.put(uri.toString(), sliceLiveData); } + mCardSet.add(card); sliceLiveData.removeObservers(mLifecycleOwner); sliceLiveData.observe(mLifecycleOwner, slice -> { @@ -128,14 +135,27 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove); btnRemove.setOnClickListener(v -> { - mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed( - card); + mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card); }); } @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(); + contexualCardFeatureProvider.logContextualCardClick(mContext, card, + eventInfo.rowIndex, eventInfo.actionType); + break; + } + } } public static class SliceViewHolder extends RecyclerView.ViewHolder { diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 02468b8ad06..a52619b3a6f 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -28,6 +28,7 @@ import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.gestures.AssistGestureFeatureProvider; +import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider; import com.android.settings.panel.PanelFeatureProvider; import com.android.settings.search.SearchFeatureProvider; @@ -108,6 +109,8 @@ public abstract class FeatureFactory { public abstract PanelFeatureProvider getPanelFeatureProvider(); + public abstract ContextualCardFeatureProvider getContextualCardFeatureProvider(); + public static final class FactoryNotFoundException extends RuntimeException { public FactoryNotFoundException(Throwable throwable) { super("Unable to create factory. Did you misconfigure Proguard?", throwable); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index 8d6d4b69fb6..3515ac06a48 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -40,6 +40,8 @@ import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProviderImpl; import com.android.settings.gestures.AssistGestureFeatureProvider; 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.LocaleFeatureProviderImpl; import com.android.settings.panel.PanelFeatureProvider; @@ -75,6 +77,7 @@ public class FeatureFactoryImpl extends FeatureFactory { private SlicesFeatureProvider mSlicesFeatureProvider; private AccountFeatureProvider mAccountFeatureProvider; private PanelFeatureProvider mPanelFeatureProvider; + private ContextualCardFeatureProvider mContextualCardFeatureProvider; @Override public SupportFeatureProvider getSupportFeatureProvider(Context context) { @@ -220,4 +223,11 @@ public class FeatureFactoryImpl extends FeatureFactory { } return mPanelFeatureProvider; } + + public ContextualCardFeatureProvider getContextualCardFeatureProvider() { + if (mContextualCardFeatureProvider == null) { + mContextualCardFeatureProvider = new ContextualCardFeatureProviderImpl(); + } + return mContextualCardFeatureProvider; + } } diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index 9bba3c331d9..c5c552e54e1 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -73,6 +73,11 @@ fake_package/fake_service + + + aaa.bbb.ccc + + com.android.settings.slice_whitelist_package diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java new file mode 100644 index 00000000000..08631f7bdfc --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImplTest.java @@ -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); + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java index 29e309d9620..af3b2e8b015 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardControllerTest.java @@ -18,9 +18,10 @@ package com.android.settings.homepage.contextualcards.slices; 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.spy; +import static org.mockito.Mockito.verify; import android.content.ContentResolver; 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.ContextualCardFeedbackDialog; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; @@ -57,6 +59,7 @@ public class SliceContextualCardControllerTest { private CardContentProvider mProvider; private ContentResolver mResolver; private SliceContextualCardController mController; + private FakeFeatureFactory mFeatureFactory; @Before public void setUp() { @@ -67,6 +70,7 @@ public class SliceContextualCardControllerTest { mProvider); mResolver = mContext.getContentResolver(); mController = spy(new SliceContextualCardController(mContext)); + mFeatureFactory = FakeFeatureFactory.setupForTest(); } @Test @@ -75,7 +79,8 @@ public class SliceContextualCardControllerTest { mResolver.insert(providerUri, generateOneRow()); 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 selection = CardDatabaseHelper.CardColumns.NAME + "=?"; @@ -86,6 +91,8 @@ public class SliceContextualCardControllerTest { cr.close(); assertThat(qryDismissed).isEqualTo(1); + verify(mFeatureFactory.mContextualCardFeatureProvider).logContextualCardDismiss( + mContext, card); } @Test diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index e14ef1f4b9d..978dd7d30f5 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -28,6 +28,7 @@ import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; import com.android.settings.fuelgauge.PowerUsageFeatureProvider; import com.android.settings.gestures.AssistGestureFeatureProvider; +import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider; import com.android.settings.localepicker.LocaleFeatureProvider; import com.android.settings.overlay.DockUpdaterFeatureProvider; import com.android.settings.overlay.FeatureFactory; @@ -63,6 +64,7 @@ public class FakeFeatureFactory extends FeatureFactory { public final AssistGestureFeatureProvider assistGestureFeatureProvider; public final AccountFeatureProvider mAccountFeatureProvider; public final PanelFeatureProvider mPanelFeatureProvider; + public final ContextualCardFeatureProvider mContextualCardFeatureProvider; public SlicesFeatureProvider slicesFeatureProvider; public SearchFeatureProvider searchFeatureProvider; @@ -105,6 +107,7 @@ public class FakeFeatureFactory extends FeatureFactory { slicesFeatureProvider = mock(SlicesFeatureProvider.class); mAccountFeatureProvider = mock(AccountFeatureProvider.class); mPanelFeatureProvider = mock(PanelFeatureProvider.class); + mContextualCardFeatureProvider = mock(ContextualCardFeatureProvider.class); } @Override @@ -191,4 +194,8 @@ public class FakeFeatureFactory extends FeatureFactory { public PanelFeatureProvider getPanelFeatureProvider() { return mPanelFeatureProvider; } + + public ContextualCardFeatureProvider getContextualCardFeatureProvider() { + return mContextualCardFeatureProvider; + } }