Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 40976d46 authored by Raff Tsai's avatar Raff Tsai Committed by Fan Zhang
Browse files

Record all contextual card log to MetricsFeatureProvider

Use ContextualCardLogUtils to serialize contextual card event to
string, and records the string using regular MetricFeatureProvider
logging APIs.

Bug: 124701288
Test: Robolectric, integrating test with SettingsIntelligence
Change-Id: Ie139b4f4b8a2b0f0dcc4bb8df9bdec8f5fd824a6
parent 8b3cde0f
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.settings.slices.CustomSliceRegistry.BLUETOOTH_DEVICES_
import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_NOTIFICATION_CHANNEL_SLICE_URI;
import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -32,7 +33,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.homepage.contextualcards.logging.ContextualCardLogUtils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.AsyncLoaderCompat;

import java.util.ArrayList;
@@ -162,10 +165,16 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
            return visibleCards;
        } finally {
            if (!CardContentProvider.DELETE_CARD_URI.equals(mNotifyUri)) {
                final ContextualCardFeatureProvider contextualCardFeatureProvider =
                        FeatureFactory.getFactory(mContext)
                                .getContextualCardFeatureProvider(mContext);
                contextualCardFeatureProvider.logContextualCardDisplay(visibleCards, hiddenCards);
                final MetricsFeatureProvider metricsFeatureProvider =
                        FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();

                metricsFeatureProvider.action(mContext,
                        SettingsEnums.ACTION_CONTEXTUAL_CARD_SHOW,
                        ContextualCardLogUtils.buildCardListLog(visibleCards));

                metricsFeatureProvider.action(mContext,
                        SettingsEnums.ACTION_CONTEXTUAL_CARD_NOT_SHOW,
                        ContextualCardLogUtils.buildCardListLog(hiddenCards));
            }
        }
    }
+263 −0
Original line number Diff line number Diff line
/*
 * 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.logging;

import android.util.Log;

import androidx.slice.widget.EventInfo;

import com.android.settings.homepage.contextualcards.ContextualCard;

import java.util.ArrayList;
import java.util.List;

/**
 * Utils of building contextual card to string, and parse string back to {@link CardLog}
 */
public class ContextualCardLogUtils {

    private static final String TAG = "ContextualCardLogUtils";

    private static final class TapTarget {
        static int TARGET_DEFAULT = 0;
        static int TARGET_TITLE = 1;
        static int TARGET_TOGGLE = 2;
        static int TARGET_SLIDER = 3;
    }

    /**
     * Log data for a general contextual card event
     */
    public static class CardLog {
        private final String mSliceUri;
        private final double mRankingScore;

        public CardLog(Builder builder) {
            mSliceUri = builder.mSliceUri;
            mRankingScore = builder.mRankingScore;
        }

        public String getSliceUri() {
            return mSliceUri;
        }

        public double getRankingScore() {
            return mRankingScore;
        }

        public static class Builder {
            private String mSliceUri;
            private double mRankingScore;

            public Builder setSliceUri(String sliceUri) {
                mSliceUri = sliceUri;
                return this;
            }

            public Builder setRankingScore(double rankingScore) {
                mRankingScore = rankingScore;
                return this;
            }
            public CardLog build() {
                return new CardLog(this);
            }
        }
    }

    /**
     * Log data for a contextual card click event
     */
    public static class CardClickLog extends CardLog {
        private final int mSliceRow;
        private final int mSliceTapTarget;
        private final int mUiPosition;

        public CardClickLog(Builder builder) {
            super(builder);
            mSliceRow = builder.mSliceRow;
            mSliceTapTarget = builder.mSliceTapTarget;
            mUiPosition = builder.mUiPosition;
        }

        public int getSliceRow() {
            return mSliceRow;
        }

        public int getSliceTapTarget() {
            return mSliceTapTarget;
        }

        public int getUiPosition() {
            return mUiPosition;
        }

        public static class Builder extends CardLog.Builder {
            private int mSliceRow;
            private int mSliceTapTarget;
            private int mUiPosition;

            public Builder setSliceRow(int sliceRow) {
                mSliceRow = sliceRow;
                return this;
            }

            public Builder setSliceTapTarget(int sliceTapTarget) {
                mSliceTapTarget = sliceTapTarget;
                return this;
            }

            public Builder setUiPosition(int uiPosition) {
                mUiPosition = uiPosition;
                return this;
            }
            @Override
            public CardClickLog build() {
                return new CardClickLog(this);
            }
        }
    }

    /**
     * Serialize {@link ContextualCard} click event to string
     *
     * @param card Clicked Contextual card.
     * @param sliceRow A Slice can contains multiple row, which row are we clicked
     * @param tapTarget Integer value of {@link TapTarget}
     * @param uiPosition Contextual card position in Listview
     */
    public static String buildCardClickLog(ContextualCard card, int sliceRow, int tapTarget,
            int uiPosition) {
        final StringBuilder log = new StringBuilder();
        log.append(card.getTextSliceUri()).append("|")
                .append(card.getRankingScore()).append("|")
                .append(sliceRow).append("|")
                .append(actionTypeToTapTarget(tapTarget)).append("|")
                .append(uiPosition);
        return log.toString();
    }

    /**
     * Parse string to a {@link CardClickLog}
     */
    public static CardClickLog parseCardClickLog(String clickLog) {
        if (clickLog != null) {
            final String[] parts = clickLog.split("\\|");
            if (parts.length < 5) {
                return null;
            }
            try {
                final CardClickLog.Builder builder = new CardClickLog.Builder();
                builder.setSliceRow(Integer.parseInt(parts[2]))
                        .setSliceTapTarget(Integer.parseInt(parts[3]))
                        .setUiPosition(Integer.parseInt(parts[4]))
                        .setSliceUri(parts[0])
                        .setRankingScore(Double.parseDouble(parts[1]));
                return builder.build();
            } catch (Exception e) {
                Log.e(TAG, "error parsing log", e);
                return null;
            }
        }
        return null;
    }

    /**
     * Serialize {@link ContextualCard} to string
     *
     * @param card Contextual card.
     */
    public static String buildCardDismissLog(ContextualCard card) {
        final StringBuilder log = new StringBuilder();
        log.append(card.getTextSliceUri())
                .append("|")
                .append(card.getRankingScore());
        return log.toString();
    }

    /**
     * Parse string to a {@link CardLog}
     */
    public static CardLog parseCardDismissLog(String dismissLog) {
        if (dismissLog != null) {
            final String[] parts = dismissLog.split("\\|");
            if (parts.length < 2) {
                return null;
            }
            try {
                final CardLog.Builder builder = new CardLog.Builder();
                builder.setSliceUri(parts[0])
                        .setRankingScore(Double.parseDouble(parts[1]));
                return builder.build();
            } catch (Exception e) {
                Log.e(TAG, "error parsing log", e);
                return null;
            }
        }
        return null;
    }

    /**
     * Serialize List of {@link ContextualCard} to string
     */
    public static String buildCardListLog(List<ContextualCard> cards) {
        final StringBuilder log = new StringBuilder();
        log.append(cards.size());
        for (ContextualCard card : cards) {
            log.append("|").append(card.getTextSliceUri())
                    .append("|").append(card.getRankingScore());
        }
        return log.toString();
    }

    /**
     * Parse string to a List of {@link CardLog}
     */
    public static List<CardLog> parseCardListLog(String listLog) {
        final List<CardLog> logList = new ArrayList<>();
        try {
            final String[] parts = listLog.split("\\|");
            if (Integer.parseInt(parts[0]) < 0) {
                return logList;
            }
            final int size = parts.length;
            for (int i = 1; i < size; ) {
                final CardLog.Builder builder = new CardLog.Builder();
                builder.setSliceUri(parts[i++])
                        .setRankingScore(Double.parseDouble(parts[i++]));
                logList.add(builder.build());
            }
        } catch (Exception e) {
            Log.e(TAG, "error parsing log", e);
            return logList;
        }
        return logList;
    }

    public static int actionTypeToTapTarget(int actionType) {
        switch (actionType) {
            case EventInfo.ACTION_TYPE_CONTENT:
                return TapTarget.TARGET_TITLE;
            case EventInfo.ACTION_TYPE_TOGGLE:
                return TapTarget.TARGET_TOGGLE;
            case EventInfo.ACTION_TYPE_SLIDER:
                return TapTarget.TARGET_SLIDER;
            default:
                Log.w(TAG, "unknown type " + actionType);
                return TapTarget.TARGET_DEFAULT;
        }
    }
}
+10 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.homepage.contextualcards.slices;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
@@ -27,10 +28,11 @@ 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.logging.ContextualCardLogUtils;
import com.android.settings.homepage.contextualcards.ContextualCardUpdateListener;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;

/**
@@ -70,9 +72,13 @@ public class SliceContextualCardController implements ContextualCardController {
            dbHelper.markContextualCardAsDismissed(mContext, card.getName());
        });
        showFeedbackDialog(card);
        final ContextualCardFeatureProvider contextualCardFeatureProvider =
                FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext);
        contextualCardFeatureProvider.logContextualCardDismiss(card);

        final MetricsFeatureProvider metricsFeatureProvider =
                FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();

        metricsFeatureProvider.action(mContext,
                SettingsEnums.ACTION_CONTEXTUAL_CARD_DISMISS,
                ContextualCardLogUtils.buildCardDismissLog(card));
    }

    @Override
+10 −4
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.homepage.contextualcards.slices;

import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.util.Log;
import android.view.View;
@@ -33,8 +34,9 @@ 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.homepage.contextualcards.logging.ContextualCardLogUtils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;

/**
 * Card renderer helper for {@link ContextualCard} built as slice deferred setup card.
@@ -65,10 +67,14 @@ class SliceDeferredSetupCardRendererHelper {
            } 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 */,
            final String log = ContextualCardLogUtils.buildCardClickLog(card, 0 /* row */,
                    EventInfo.ACTION_TYPE_CONTENT, view.getAdapterPosition());

            final MetricsFeatureProvider metricsFeatureProvider =
                    FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();

            metricsFeatureProvider.action(mContext,
                    SettingsEnums.ACTION_CONTEXTUAL_CARD_CLICK, log);
        });
    }

+10 −5
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.homepage.contextualcards.slices;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.view.View;
import android.widget.LinearLayout;
@@ -26,8 +27,9 @@ 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.logging.ContextualCardLogUtils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;

/**
 * Card renderer helper for {@link ContextualCard} built as slice full card.
@@ -54,11 +56,14 @@ class SliceFullCardRendererHelper {
        // Set this listener so we can log the interaction users make on the slice
        cardHolder.sliceView.setOnSliceActionListener(
                (eventInfo, sliceItem) -> {
                    final ContextualCardFeatureProvider contextualCardFeatureProvider =
                            FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(
                                    mContext);
                    contextualCardFeatureProvider.logContextualCardClick(card, eventInfo.rowIndex,
                    final String log = ContextualCardLogUtils.buildCardClickLog(card, eventInfo.rowIndex,
                            eventInfo.actionType, cardHolder.getAdapterPosition());

                    final MetricsFeatureProvider metricsFeatureProvider =
                            FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();

                    metricsFeatureProvider.action(mContext,
                            SettingsEnums.ACTION_CONTEXTUAL_CARD_CLICK, log);
                });

        // Customize slice view for Settings
Loading